
#include "xrtc_endpoint.h"

#include <cinttypes> // for printing int64_t
#include <functional>
#include <random>
#include <glib.h>
#include <agent.h>
#include <cstring>
#include <regex>
#include <climits>
#include <nice/nice.h>

//#include "bitrate_estimator.hpp"
#include "bwe/estimator.hpp"
#include "batch_sender.hpp"
#include "h264/h264_nal.hpp"
#include "nack_handler.hpp"
#include "rtcp_handler.h"
#include "rtp/rtp_exten.hpp"
#include "rtp/rtp_factory.hpp"
#include "rtp/rtp_map.hpp"
#include "rtp/rtcp/transport_feedback.hpp"
#include "ulpfec_decoder.hpp"
#include "xm_log.h"
#include "xdtls_srtp.h"
#include "xsdp.h"
#include "xcutil.h"
#include "xm_cache.h"
#include "xrtc_endpoint_connector.h"
#include "xrtcp_util.h"
#include "codecs.hpp"
#include "placeholder.hpp"
#include "xm_app_config.h"


#define rtc_dbgv(FMT, ARGS...) dbgv("|%s| " FMT, rtc->obj_id, ##ARGS )
#define rtc_dbgd(FMT, ARGS...) dbgd("|%s| " FMT, rtc->obj_id, ##ARGS )
#define rtc_dbgi(FMT, ARGS...) dbgi("|%s| " FMT, rtc->obj_id, ##ARGS )
#define rtc_dbge(FMT, ARGS...) dbge("|%s| " FMT, rtc->obj_id, ##ARGS )

#define nack_dbgi(...)  rtc_dbgd(__VA_ARGS__)


enum rtc_role_type{
    RTC_ROLE_NONE = 0,
    RTC_ROLE_OFFER,
    RTC_ROLE_ANSWER
};

enum rtc_sdp_state{
    SDP_STATE_NONE = 0,
    SDP_STATE_OFFER,
    SDP_STATE_PRANSWER,
    SDP_STATE_ANSWER,
    SDP_STATE_MAX
};

#define RTC_RTP  0
#define RTC_RTCP 1

/* RTCP Packet Types (http://www.networksorcery.com/enp/protocol/rtcp.htm) */
enum {
    RTCP_FIR = 192,
    RTCP_NACK = 193,
    RTCP_SR = 200,
    RTCP_RR = 201,
    RTCP_SDES = 202,
    RTCP_BYE = 203,
    RTCP_APP = 204,
    RTCP_RTPFB = 205,
    RTCP_PSFB = 206,
} ;

static constexpr int SDP_MAX = 8192;


typedef struct rtc_codec_{
    const char *name;
    int type;
    int pt;
    int samplerate;
    int nch;
    int is_video_codec;
}rtc_codec;

typedef struct rtc_nice_component{
    int enabled;
    int component_id;
    int candidate_count;
    gchar ** candidate_strings;
}rtc_nice_component;

typedef struct rtc_transport_source{
    uint32_t src_id;
    uint64_t packet_count;
    uint32_t rtt; // subscriber reported rtt
    uint32_t jitter;
    uint32_t hseq;
    uint32_t loss_packet_nb_q8;
    uint32_t total_packet_nb;
    uint32_t lost_cum;
    uint64_t rtcp_recv_ts;
    uint32_t remb_bitrate;
    uint32_t pub_remb_bitrate;
    // last_rtcp_ntp/rtp_ts are used in publisher bwe
    uint32_t last_rtcp_ts;
    uint64_t last_rtcp_ms;
}rtc_transport_source;


typedef struct rtc_nice_transport{
    rtc_endpoint_t owner;
    int stream_type;
    int enabled;
    const char * mid;
    int mlineindex;   // there is no mline in sdp if < 0
    xdtls_srtp_t dtls;
    GSource * dtls_timer;
    int stream_id;
    int local_candidate_done;
    rtc_nice_component comps[2];
    char *local_ufrag;
    char *local_password;
    
    rtc_transport_source local_sources[2];
    rtc_transport_source remote_sources[2];
    xmcache_t sent_cache; // encrypt data sent to remote
    xmcache_t nack_req_cache; // nack-seq sent to remote
    
    int dtls_kick_num;
    int64_t dtls_kick_time;
    int64_t dtls_recv_packets;
    int64_t dtls_sent_packets;
    
    int is_orgin_seq_valid;
    uint32_t orgin_rtp_seq_base;
    uint32_t sent_rtp_seq_base;
    uint32_t last_sent_rtp_seq;
    
    uint64_t recv_rtp_packets;
    uint64_t recv_rtp_payload_bytes;
    
    uint64_t sent_rtp_packets;
    uint64_t sent_rtp_payload_bytes;
    
    uint32_t ntp_msw;
    uint32_t ntp_lsw;
    uint32_t rtp_ts;
    //uint32_t remb_bitrate;

    uint32_t next_rtx_seq;
    nack_handler_t nacks;
    rtcp_handler_t rtcp_handler;

    uint16_t transport_seq_num;
    uint64_t send_bytes_count;
    uint64_t recv_bytes_count;

    // do not reset following recv/send counter, they are related to billing
    uint64_t recv_rtp_bytes;
    uint64_t recv_rtcp_bytes;
    uint64_t recv_unknown_bytes;
    uint64_t recv_dtls_bytes;
    uint64_t recv_ice_bytes;
    // do not reset following recv/send counter, they are related to billing
    uint64_t send_rtp_bytes;
    uint64_t send_rtcp_bytes;
    uint64_t send_unknown_bytes;
    uint64_t send_dtls_bytes;
    uint64_t send_ice_bytes;
    
    bool enable_CNSL;
    bool CNSL_lv;
}rtc_nice_transport;

struct rtc_endpoint_st {
//    std::list<rtc_endpoint_t>::iterator ref;
//    rtc_manager_t owner;
    gint64 create_ts;

    char* obj_id;
    int obj_id_len;
    GMainContext * main_context;
    const char * media_ip;
    int min_port;
    int max_port;
    NiceAgent *agent;
    rtc_role_type rtc_role;
    
//    int seq;
    
    rtc_sdp_state remote_sdp_state;
    xsdpinfo * remote_sdpinfo;
    int local_stream_count;
    rtc_codecs_t* codecs;
    RtpExtensionMap* rtp_extens_audio;
    RtpExtensionMap* rtp_extens_video;

    char local_sdp[SDP_MAX];
    int local_sdp_len;
    rtc_nice_transport * audio_xtrans;
    rtc_nice_transport * video_xtrans;
    rtc_nice_transport xtranses[2];
    rtc_stream_type destroy_xtranse_type;
    int is_local_bundle;
    
//    GSList * listeners;
    GQueue * listeners;
    uint8_t recv_work_buf[1600];
    uint8_t send_work_buf[1600];

    rtc_connector_t connector;
    rtc_endpoint_t pub_rtc;
    rtc_linker_t sub_linker;
    GSource * check_timer;
    int is_send_report;
    int64_t last_report_time;
    
    rtcp_parser_t rtcp_parser;
    
    char filename[512];
    tlv_file_t tlv1;
    bool tlv_with_raw_packet;
    
    uint64_t live_seq;
    uint64_t last_live_seq;
    int64_t last_check_live_time;
    std::map<std::string, void*>* user_data;
    int is_setup;
    int max_video_bitrate;
    int min_video_bitrate;
    bool is_dumping_rtp;
    bool is_dumping_rtcp;
    bool is_translating_fec;
    RtpFactory* rtp_factory = nullptr;
    UlpfecDecoder* ulpfec_decoder = nullptr;

    bool enable_fec;
    bool disable_h264 = false;
    uint8_t sps_pps_buf[1600];
    size_t sps_pps_len;
    bool compensate_rtcp = false;
    bool experiment_esmb = false;
    bool lost_0 = false;
    bool enable_bwe = false;
    bool force_record;

    bwe::InterArrivalEstimator* sub_estimator = nullptr;
    bwe::InterArrivalEstimator* pub_estimator = nullptr;
    uint32_t remote_audio_ssrc;
    bool remote_audio_ssrc_overrided;
    bool force_send_sr;
    bool enable_nack_handler;

    BatchSender* batch;

    bool drop_audio;
    bool drop_video;

    // used to reduce remb logs
    int remb_log_counter;

//    std::vector<uint32_t> relay_audio_ssrcs;
//    std::vector<uint32_t> relay_video_ssrcs;
};
static inline int send_rtcp_data(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans, rtc_nice_transport * xtrans, unsigned char * buf, int len);
// static void select_audio_codecs(RtpMap &rtp_map, GSList *sdp_rtpmaps);
// static void sdp_append_audio(rtc_endpoint_t rtc, RtpMap& rtp_map, const char* mid, const char* ice_ufrag, const char* ice_passwd, const char* dtls_fingerprint, int is_rtcp_mux, int is_pranswer);
// static void sdp_append_audio_ssrc(rtc_endpoint_t rtc);
// static void select_video_codecs(RtpMap &rtp_map, GSList *sdp_payloads, GSList *sdp_rtpmaps, GSList *sdp_fmtps);
// static void sdp_append_video(rtc_endpoint_t rtc, const RtpMap& rtp_map, const char* mid, const char* ice_ufrag, const char* ice_pwd, const char* fingerprint, bool is_rtcp_mux, int is_pranswer);
// static void sdp_append_video_ssrc(rtc_endpoint_t rtc);
static const char* _rtc_endpoint_negotiate_sendrecv(const xsdp_stream* stream);
static inline int send_rtp_data(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans, rtc_nice_transport * xtrans, rtc_codec_id_t codec_id, rtc_codec_id_t fec_codec_id, unsigned char * buf, int len, bool is_rtx);
static void negotiate(rtc_endpoint_t rtc);

void dump_rtp_extensions(rtc_endpoint_st *rtc, const uint8_t *buf, size_t len);

static void replace_rtcp_ssrc_to_placeholder(unsigned char *packet, int len, placeholder::stream, rtc_nice_transport*);

static void replace_rtcp_placeholder_to_ssrc(unsigned char *packet, int len, placeholder::stream, rtc_nice_transport*);

int parse_rtp_header_extension(unsigned char* buf, int size, std::function<bool(uint8_t id, unsigned char* buf, int size)> callback);


// TODO:
static const gchar *candidate_type_name[] = {"host", "srflx", "prflx", "relay"};
static const gchar *state_name[] = {"disconnected", "gathering", "connecting",
    "connected", "ready", "failed"};


static inline
uint64_t get_next_rtc_id(){
    static uint64_t next_id = 0;
    return ++next_id;
}

static inline
char generate_random_char(){
    static std::random_device rd;
    return 'a' + rd()%26;
}
static inline
int generate_obj_id(char * buf, size_t bufsz){
    return snprintf(buf, bufsz, "rtc-%lu-%c%c%c"
                    , get_next_rtc_id()
                    , generate_random_char()
                    , generate_random_char()
                    , generate_random_char()
             );
}


static
int print_local_data (rtc_endpoint_t rtc, NiceAgent *agent, guint stream_id, guint component_id)
{
    int result = EXIT_FAILURE;
    gchar *local_ufrag = NULL;
    gchar *local_password = NULL;
    gchar ipaddr[INET6_ADDRSTRLEN];
    GSList *cands = NULL, *item;
    
    if (!nice_agent_get_local_credentials(agent, stream_id,
                                          &local_ufrag, &local_password))
        goto end;
    
    cands = nice_agent_get_local_candidates(agent, stream_id, component_id);
    if (cands == NULL)
        goto end;
    
    rtc_dbgi("local_ufrag = %s", local_ufrag);
    rtc_dbgi("local_password = %s", local_password);
    
    for (item = cands; item; item = item->next) {
        NiceCandidate *c = (NiceCandidate *)item->data;
        
        nice_address_to_string(&c->addr, ipaddr);
        
        //         see rfc5245#section-15
        //        candidate-attribute   = "candidate" ":" foundation SP component-id SP
        //        transport SP
        //        priority SP
        //        connection-address SP     ;from RFC 4566
        //        port         ;port from RFC 4566
        //        SP cand-type
        //        [SP rel-addr]
        //        [SP rel-port]
        //        *(SP extension-att-name SP
        //          extension-att-value)
        //
        //        foundation            = 1*32ice-char
        //        component-id          = 1*5DIGIT
        //        transport             = "UDP" / transport-extension
        //        transport-extension   = token              ; from RFC 3261
        //        priority              = 1*10DIGIT
        //        cand-type             = "typ" SP candidate-types
        //        candidate-types       = "host" / "srflx" / "prflx" / "relay" / token
        //        rel-addr              = "raddr" SP connection-address
        //        rel-port              = "rport" SP port
        //        extension-att-name    = byte-string    ;from RFC 4566
        //        extension-att-value   = byte-string
        //        ice-char              = ALPHA / DIGIT / "+" / "/"
        
        // a=candidate:1 1 UDP 2013266431 172.17.1.197 62218 typ host
        // a=candidate:1467250027 1 udp 2122260223 192.168.0.196 46243 typ host generation 0
        //   candidate:4279727527 1 udp 2122260223 172.17.3.234 62705 typ host generation 0 ufrag 7y\\/YrTaZ8T4q2qgI
        // 4279727527       -> foundation
        // 1                -> component-id
        // udp              -> transport
        // 2122260223       -> priority
        // 172.17.3.234     -> connection-address
        // 62705            -> port
        // typ host         -> candidate-types
        
        // 1,2013266431,172.17.1.197,54605,host
        // (foundation),(prio),(addr),(port),(type)
        
        rtc_dbgi("  %s,%u,%s,%u,%s",
             c->foundation,
             c->priority,
             ipaddr,
             nice_address_get_port(&c->addr),
             candidate_type_name[c->type]);
        
        //        nice_agent_set_stream_name (agent, 1, "audio");
        ////        gchar * cand_sdp = nice_agent_generate_local_sdp(agent);
        //        gchar * cand_sdp = nice_agent_generate_local_candidate_sdp (agent, c);
        //        rtc_dbgi(" cand_sdp = %s", cand_sdp);
        //        g_free(cand_sdp);
    }
    result = EXIT_SUCCESS;
    
end:
    if (local_ufrag)
        g_free(local_ufrag);
    if (local_password)
        g_free(local_password);
    if (cands)
        g_slist_free_full(cands, (GDestroyNotify)&nice_candidate_free);
    
    return result;
}

static inline
void rtc_endpoint_heart_beat(rtc_endpoint_t rtc, const char * reason){
    rtc->live_seq++;
//    if(reason && memcmp(rtc->obj_id, "rtc-2", 5)==0){// dddddddd
//        rtc_dbgi("live heart beat: [%s]", reason);
//    }
    
}

static
void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id,
                                 gpointer data){
    rtc_endpoint_t rtc = (rtc_endpoint_t) data;
    rtc_dbgv("candidate gathering done, stream=%d", stream_id);
    rtc_endpoint_heart_beat(rtc, "gather-done");
    rtc->xtranses[stream_id-1].local_candidate_done = 1;
    print_local_data(rtc, agent, stream_id, 1);
}

static inline
void maybe_kick_stream_dtls(rtc_endpoint_t rtc, guint stream_id){
    if(rtc->rtc_role == RTC_ROLE_ANSWER && rtc->xtranses[stream_id-1].enabled){
        rtc_dbgi("kick dtls handshake, stream_id=%d", stream_id);
        rtc->xtranses[stream_id-1].dtls_kick_num++;
        rtc->xtranses[stream_id-1].dtls_kick_time = get_timestamp_ms();
        xdtls_srtp_start_handshake(rtc->xtranses[stream_id-1].dtls, 0);
        
    }
}

static
void cb_new_selected_pair(NiceAgent *agent, guint stream_id,
                          guint component_id, gchar *lfoundation,
                          gchar *rfoundation, gpointer data)
{
    rtc_endpoint_t rtc = (rtc_endpoint_t) data;
    rtc_dbgi("selected pair: stream_id=%d, comp_id=%d, local_founda=[%s], remote_founda=[%s]", stream_id, component_id,  lfoundation, rfoundation);
    rtc_endpoint_heart_beat(rtc, "sel-pair");
    maybe_kick_stream_dtls(rtc, stream_id);

}
static
void cb_component_state_changed(NiceAgent *agent, guint stream_id,
                                guint component_id, guint state,
                                gpointer data)
{
    rtc_endpoint_t rtc = (rtc_endpoint_t) data;
    rtc_dbgi("ice state changed [stream %d, component %d]: %s(%d)",
         stream_id, component_id, state_name[state], state);
    rtc_endpoint_heart_beat(rtc, "state-changed");
    if(state == 3 || state == 4){
        maybe_kick_stream_dtls(rtc, stream_id);
    }
}

static void dump_hex(const char* prefix, const unsigned char* buf, int len) {
//    if (len > 1024) {
//        len = 1024;
//    }
//
//    char hex[1024 * 3] = {0,};
//
//    for (auto i = 0; i < len; i += 1) {
//        sprintf(hex + i * 3, " %02x", buf[i]);
//    }
//
//    dbgi("%s %s", prefix, hex);
}

static void hex_print(const char * prefix, const unsigned char * data, int length){
    static char str_buf[65535] = {0,};
    char* p = str_buf;
    int i = 0;

    for(i = 0; i < length; i++) {
        p += sprintf(p, "%.2X:", data[i]);
    }
    *(p-1) = 0;

    dbgi("%s %s", prefix, str_buf);
}

static
void dump_rtp(rtc_endpoint_t rtc, const char * prefix, void* buf_, int len ){
    const unsigned char * buf = (const unsigned char *)buf_;
    // 	char prefix[16];
    // 	sprintf(prefix, "No.%d", packet_count);
    // 	if(len < 12){
    // 		rtc_dbgi("%s rtp len too small %d", prefix, len);
    // 		return;
    // 	}
    
    unsigned char v =  (buf[0]>>6) & 0x3;
    unsigned char p =  (buf[0]>>5) & 0x1;
    unsigned char x =  (buf[0]>>4) & 0x1;
    unsigned char cc = (buf[0]>>0) & 0xF;
    unsigned char m =  (buf[1]>>7) & 0x1;
    unsigned char pt = (buf[1]>>0) & 0x7F;
    unsigned short seq = be_get_u16(buf+2);
    unsigned int ts = be_get_u32(buf+4);
    unsigned int ssrc = be_get_u32(buf+8);
    
    int header_len = 12 + cc * 4;
    int ext_len = 0;
    if (x && len > header_len + 4) {
        ext_len = be_get_u16(buf+header_len+2);
        header_len += 4 + 4 * ext_len;
    }

    int payload_len = len - header_len;
    rtc_dbgi("%s rtp[v=%u,p=%u,x=%u,cc=%u,m=%u,pt=%u,seq=%u,ts=%u,ssrc=%u,pl=%d]", prefix,
             v, p, x, cc, m, pt, seq, ts, ssrc, payload_len);

    if (x == 1) {
        dump_rtp_extensions(rtc, buf + 12 + 4 * cc, 4 + 4 * ext_len);
    }

    //hex_print("total", buf, std::min(64, len));
}

static
void dump_rtp_red(rtc_endpoint_t rtc, const char * prefix, void* buf_, int len ){
    const unsigned char * buf = (const unsigned char *)buf_;
    // 	char prefix[16];
    // 	sprintf(prefix, "No.%d", packet_count);
    // 	if(len < 12){
    // 		rtc_dbgi("%s rtp len too small %d", prefix, len);
    // 		return;
    // 	}

    unsigned char v =  (buf[0]>>6) & 0x3;
    unsigned char p =  (buf[0]>>5) & 0x1;
    unsigned char x =  (buf[0]>>4) & 0x1;
    unsigned char cc = (buf[0]>>0) & 0xF;
    unsigned char m =  (buf[1]>>7) & 0x1;
    unsigned char pt = (buf[1]>>0) & 0x7F;
    unsigned short seq = be_get_u16(buf+2);
    unsigned int ts = be_get_u32(buf+4);
    unsigned int ssrc = be_get_u32(buf+8);

    int header_len = 12 + cc * 4;
    int ext_len = 0;
    if (x) {
        int min = header_len + 4;
        if(min <= len){
            ext_len = be_get_u16(buf+header_len+2);
            header_len += 4+ext_len*4;
        }
    }
    uint16_t payload_len = len - header_len;
    uint8_t more_than_one_block = buf[header_len] >> 7;
    uint16_t block_len = len - header_len - 1;
    if (more_than_one_block) {
        block_len = buf[header_len + 1] << 6;
        block_len = block_len << 2;
        block_len |= buf[header_len + 2];
    }
    uint8_t block_pt = buf[header_len] & 0x7Fu;


    rtc_dbgi("%s red[v=%u,p=%u,x=%u,cc=%u,m=%u,pt=%u,seq=%u,ts=%u,ssrc=%u,pl=%u,block[0].pt=%u,block[0].len=%u,block[0].more=%u,len=%u,payload=0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x ...]", prefix
    , v, p, x, cc, m, pt, seq, ts, ssrc, payload_len, block_pt, block_len, more_than_one_block, len,
             buf[header_len + 0], buf[header_len + 1], buf[header_len + 2], buf[header_len + 3],
             buf[header_len + 4], buf[header_len + 5], buf[header_len + 6], buf[header_len + 7]);
    if (x == 1) {
        dump_rtp_extensions(rtc, buf + 12 + 4 * cc, 4 + 4 * ext_len);
    }

    //hex_print("total", buf, std::min(64, len));
}

void dump_rtp_extensions(rtc_endpoint_t rtc, const uint8_t *buf, size_t len) {
    const uint8_t* ext_read = buf + 4;
    const uint8_t* ext_end = buf + len;
    //rtc_dbgi("\t - exten length: %u", len);
    if (buf[0] == 0xBE && buf[1] == 0xDE) {
        while (ext_read < ext_end) {
            uint8_t id = ext_read[0] >> 4;
            if (id == 0) {
                ext_read++;
                continue;
            }
            if (id == 15) {
                break;
            }
            uint8_t l = ext_read[0] & 0x0F;
            static char str[32+1];
            uint8_t i = 0;
            while (i <= l+1) {
                sprintf(str + 2 * i, "%02X", ext_read[1+i]);
                i++;
            }
            str[(l+1)*2] = 0;
            rtc_dbgi("\t - exten(1 byte) id=%d l=%d value=0x%s", id, l, str);
            ext_read += l + 1 + 1;
        }
    } else if (buf[0] == 0x10 && buf[1] == 0x00) {
        while (ext_read < ext_end) {
            uint8_t id = ext_read[0];
            if (id == 0) {
                ext_read++;
                continue;
            }
            if (id == 15) {
                break;
            }
            uint8_t l = ext_read[1];
            if (l > 0) {
                static char str[512+1];
                uint8_t i = 0;
                while (i < l) {
                    sprintf(str + 2 * i, "%X", ext_read[2+i]);
                    i++;
                }
                str[l*2+1] = 0;
                rtc_dbgi("\t - exten(2 byte) id=%d l=%d value=0x%s", id, l, str);
            } else {
                rtc_dbgi("\t - exten(2 byte) id=%d l=%d", id, l);
            }
            ext_read += l + 2;
        }
    }
}

static
void dump_fec(uint8_t* buf, int len ){
    // fec header
    uint8_t e = (buf[0] >> 7) & 0x1u;
    uint8_t l = (buf[0] >> 6) & 0x1u;
    uint8_t p = (buf[0] >> 5) & 0x1u;
    uint8_t cc = buf[0] & 0xFu;
    uint8_t m = (buf[1] >> 7) & 0x1u;
    uint8_t pt = buf[1] & 0x7Fu;
    uint16_t sn = be_get_u16(buf + 2);
    uint32_t ts = be_get_u32(buf + 4);
    uint16_t lr = be_get_u16(buf + 8);

    // level header
    uint16_t protection_len = be_get_u16(buf + 10);
    uint64_t mask = be_get_u16(buf + 12);
    mask = mask << 32;
    if (l) {
        mask |= be_get_u32(buf + 14);
    }
    mask = mask << 16;
    uint8_t i = 0;
    std::ostringstream seqs;
    while (mask) {
        uint8_t most_significant_bit = (mask >> 63) & 0x1u;
        if (most_significant_bit) {
            seqs << (sn+i) << ",";
        }
        ++i;
        mask = mask << 1;
    }
    std::string seqs_str = seqs.str();
    dbgi("fec=[e=%u,l=%u,p=%u,cc=%x,m=%u,pt=%u,sn=%u,ts=%u,lr=%u,pl=%u,seqs=[%s]",
                e, l, p, cc, m, pt, sn, ts, lr, protection_len, seqs_str.c_str());
}

static
void dump_rtp_rtx(rtc_endpoint_t rtc, const char * prefix, void* buf_, int len ){
    const unsigned char * buf = (const unsigned char *)buf_;
    // 	char prefix[16];
    // 	sprintf(prefix, "No.%d", packet_count);
    // 	if(len < 12){
    // 		rtc_dbgi("%s rtp len too small %d", prefix, len);
    // 		return;
    // 	}

    unsigned char v =  (buf[0]>>6) & 0x3;
    unsigned char p =  (buf[0]>>5) & 0x1;
    unsigned char x =  (buf[0]>>4) & 0x1;
    unsigned char cc = (buf[0]>>0) & 0xF;
    unsigned char m =  (buf[1]>>7) & 0x1;
    unsigned char pt = (buf[1]>>0) & 0x7F;
    unsigned short seq = be_get_u16(buf+2);
    unsigned int ts = be_get_u32(buf+4);
    unsigned int ssrc = be_get_u32(buf+8);
    int ext_len = 0;
    int header_len = 12 + cc * 4;
    if (x) {
        int min = header_len + 4;
        if (min <= len) {
            ext_len = be_get_u16(buf+header_len+2);
            rtc_dbgi("exten=0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x", buf[header_len + 0], buf[header_len + 1], buf[header_len + 2], buf[header_len + 3],
                     buf[header_len + 4], buf[header_len + 5], buf[header_len + 6], buf[header_len + 7]);
            header_len += 4+ext_len*4;
        }
    }
    uint16_t osn = be_get_u16((buf + header_len));
    uint16_t payload_len = len - header_len - 2;
    // uint8_t first = buf[header_len + 2];
    rtc_dbgi("%s rtx[v=%u,p=%u,x=%u,cc=%u,m=%u,pt=%u,seq=%u,ts=%u,ssrc=%u,osn=%u,pl=%u,len=%u,payload=0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x ...]", prefix
    , v, p, x, cc, m, pt, seq, ts, ssrc, osn, payload_len, len,
             buf[header_len + 0], buf[header_len + 1], buf[header_len + 2], buf[header_len + 3],
             buf[header_len + 4], buf[header_len + 5], buf[header_len + 6], buf[header_len + 7]);
    if (x == 1) {
        dump_rtp_extensions(rtc, buf + 12 + 4 * cc, 4 + 4 * ext_len);
    }
    //hex_print("total", buf, std::min(32, len));
}

static
void dump_rtcp0(rtc_endpoint_t rtc, const char * prefix, void* buf_, int len ){
//    const unsigned char * buf = (const unsigned char *)buf_;
//    unsigned char v =  (buf[0]>>6) & 0x3;
//    unsigned char p =  (buf[0]>>5) & 0x1;
//    unsigned char rc = (buf[0]>>0) & 0x1F;
//    unsigned char pt = (buf[1]>>0) & 0xFF;
//    unsigned short plen = be_get_u16(buf+2);
//    unsigned int ssrc = be_get_u32(buf+4);
//    rtc_dbgi("%s rtcp[v=%u,p=%u,rc=%u,pt=%u,ssrc=%u,plen=%u/%d]", prefix
//         , v, p, rc, pt, ssrc, plen, len);
    
    
    rtc_dbgi("rtcp[len=%d] --------------", len);
    const unsigned char * rtcp = (const unsigned char *)buf_;
    int pno = 0, total = len;
    while(rtcp) {
        unsigned char v =  (rtcp[0]>>6) & 0x3;
        unsigned char p =  (rtcp[0]>>5) & 0x1;
        unsigned char rc = (rtcp[0]>>0) & 0x1F;
        unsigned char pt = (rtcp[1]>>0) & 0xFF;
        unsigned short plen = be_get_u16(rtcp+2); // packet length in 4bytes minus 1
        unsigned int ssrc = be_get_u32(rtcp+4);
        pno++;
        const char * name = "uknown";
        if(pt == RTCP_RTPFB){
            unsigned int media_ssrc = be_get_u32(rtcp+8);
            if(rc == 1){
                name = "RTPFB_NACK";
                rtc_dbgi("%s rtcp[%d]-[v=%u,p=%u,rc=%u,pt=%u,ssrc=%u,plen=%u, name=%s, mssrc=%u]", prefix
                     , pno, v, p, rc, pt, ssrc, plen, name, media_ssrc);
            }else if(rc == 2){
                name = "RTPFB_TMMBR";
            }else if(rc == 3){
                name = "RTPFB_TMMBN";
            }else if(rc == 4){
                name = "RTPFB_SRREQ";
            }else if(rc == 15){
                name = "RTPFB_FEEDBK";
            }
            rtc_dbgi("%s rtcp[%d]-[v=%u,p=%u,rc=%u,pt=%u,ssrc=%u,plen=%u, name=%s, mssrc=%u]", prefix
                 , pno, v, p, rc, pt, ssrc, plen, name, media_ssrc);
            
            
        }else if( pt == RTCP_PSFB){
            unsigned int media_ssrc = be_get_u32(rtcp+8);
            if(rc == 1){
                name = "PSFB_PLI";
            }else if(rc == 2){
                name = "PSFB_SLI";
            }else if(rc == 3){
                name = "PSFB_RPSI";
            }else if(rc == 4){
                name = "PSFB_FIR";
            }else if(rc == 15){
                name = "PSFB_APP";
            }
            rtc_dbgi("%s rtcp[%d]-[v=%u,p=%u,rc=%u,pt=%u,ssrc=%u,plen=%u, name=%s, mssrc=%u]", prefix
                 , pno, v, p, rc, pt, ssrc, plen, name, media_ssrc);
            
        }else {
            if( pt == RTCP_SR){
                name = "RTCP_SR";
            }else if(pt == RTCP_RR){
                name = "RTCP_RR";
            }else if(pt == RTCP_SDES){
                name = "RTCP_SDES";
            }else if(pt == RTCP_BYE){
                name = "RTCP_BYE";
            }
            rtc_dbgi("%s rtcp[%d]-[v=%u,p=%u,rc=%u,pt=%u,ssrc=%u,plen=%u, name=%s]", prefix
                 , pno, v, p, rc, pt, ssrc, plen, name);
        }

        
        if(plen == 0) {
//            break;
        }
        total -= plen*4+4;
        if(total <= 0) {
            break;
        }
        rtcp = (const unsigned char *)((uint32_t*)rtcp + plen + 1);
    }
    
    
}



static inline
int dump_report(rtc_endpoint_t rtc
               , rtcp_parser_t parser, const RtcpCommonHeader * header, const RTCPPacket * body){
    int num_items = 0;
    int rtcpPacketType = rtcp_parse_next_item(parser);
    while (rtcpPacketType == RTCPPacketTypes::kReportBlockItem) {
        //        HandleReportBlock(rtcpPacket, rtcpPacketInformation, remoteSSRC);
        ++num_items;
        rtc_dbgi("    - report[%d]=[ssrc=%u,fralost=%u,cumlost=%u,hseq=%u,jit=%u,lastSR=%u,delaySR=%u"
                           , num_items
                           , body->ReportBlockItem.SSRC
                           , body->ReportBlockItem.FractionLost
                           , body->ReportBlockItem.CumulativeNumOfPacketsLost
                           , body->ReportBlockItem.ExtendedHighestSequenceNumber
                           , body->ReportBlockItem.Jitter
                           , body->ReportBlockItem.LastSR
                           , body->ReportBlockItem.DelayLastSR
                           );
        rtcpPacketType = rtcp_parse_next_item(parser);
    }
    
    return rtcpPacketType;
}

static inline
int dump_SR(rtc_endpoint_t rtc
              , rtcp_parser_t parser, const RtcpCommonHeader * header, const RTCPPacket * body
              , const char * name
              , char * strbuf, int& strbufsz, int& strlen ){
    uint64_t ntp = (uint64_t)body->SR.NTPMostSignificant << 32 | body->SR.NTPLeastSignificant;
    strlen += snprintf(strbuf+strlen, strbufsz-strlen, "-[name=%s,sendssrc=%u,reports=%u,ntp=%lu,ts=%u,pkts=%u,bytes=%u]"
                        , name
                        , body->SR.SenderSSRC
                        , body->SR.NumberOfReportBlocks
                        , ntp
                        , body->SR.RTPTimestamp
                        , body->SR.SenderPacketCount
                        , body->SR.SenderOctetCount);
    
    rtc_dbgi("%s", strbuf);
    return dump_report(rtc, parser, header, body);
}

static inline
int dump_RR(rtc_endpoint_t rtc
            , rtcp_parser_t parser, const RtcpCommonHeader * header, const RTCPPacket * body
            , const char * name
            , char * strbuf, int& strbufsz, int& strlen ){
    strlen += snprintf(strbuf+strlen, strbufsz-strlen, "-[name=%s,sendssrc=%u,reports=%u]", name
                       , body->RR.SenderSSRC, body->RR.NumberOfReportBlocks);
    rtc_dbgi("%s", strbuf);
    return dump_report(rtc, parser, header, body);
}


static inline
int dump_SDES(rtc_endpoint_t rtc
               , rtcp_parser_t parser, const RtcpCommonHeader * header, const RTCPPacket * body
               , const char * name
               , char * strbuf, int& strbufsz, int& strlen ){
    
    
    strlen += snprintf(strbuf+strlen, strbufsz-strlen, "-[name=%s]", name);
    rtc_dbgi("%s", strbuf);
    strlen = 0;
    int num_items = 0;
    
    int rtcpPacketType = rtcp_parse_next_item(parser);
    while (rtcpPacketType == RTCPPacketTypes::kSdesChunk) {
        //        HandleReportBlock(rtcpPacket, rtcpPacketInformation, remoteSSRC);
        ++num_items;
        rtc_dbgi( "    - item[%d]=[ssrc=%u,cname=%s]"
                           , num_items
                           , body->CName.SenderSSRC
                           , body->CName.CName
                           );
        rtcpPacketType = rtcp_parse_next_item(parser);
    }
    
    
    return rtcpPacketType;
}

static inline
int dump_NACK(rtc_endpoint_t rtc
              , rtcp_parser_t parser, const RtcpCommonHeader * header, const RTCPPacket * body
              , const char * name
              , char * strbuf, int& strbufsz, int& strlen ){
    
    
    strlen += snprintf(strbuf+strlen, strbufsz-strlen, "-[name=%s,sendssrc=%u,mediassrc=%u]", name, body->NACK.SenderSSRC, body->NACK.MediaSSRC);
    rtc_dbgi("%s", strbuf);
    strlen = 0;
    int num_items = 0;
    
    int rtcpPacketType = rtcp_parse_next_item(parser);
    while (rtcpPacketType == RTCPPacketTypes::kRtpfbNackItem) {
        //        HandleReportBlock(rtcpPacket, rtcpPacketInformation, remoteSSRC);
        ++num_items;
        rtc_dbgi( "    - nack[%d]=[pid=%u,blp=%02X]"
                 , num_items
                 , body->NACKItem.PacketID
                 , body->NACKItem.BitMask
                 );
        rtcpPacketType = rtcp_parse_next_item(parser);
    }
    
    
    return rtcpPacketType;
}

static inline
int dump_PSFB_PLI(rtc_endpoint_t rtc
              , rtcp_parser_t parser, const RtcpCommonHeader * header, const RTCPPacket * body
              , const char * name
              , char * strbuf, int& strbufsz, int& strlen ){
    
    
    strlen += snprintf(strbuf+strlen, strbufsz-strlen, "-[name=%s,sendssrc=%u,mediassrc=%u]", name, body->PLI.SenderSSRC, body->PLI.MediaSSRC);
    rtc_dbgi("%s", strbuf);
    return rtcp_parse_next_item(parser);
}

static inline
int dump_PSFB_APP(rtc_endpoint_t rtc
                  , rtcp_parser_t parser, const RtcpCommonHeader * header, const RTCPPacket * body
                  , const char * name
                  , char * strbuf, int& strbufsz, int& strlen ){
    uint32_t sender_ssrc = body->PSFBAPP.SenderSSRC;
    uint32_t media_ssrc = body->PSFBAPP.MediaSSRC;
    int pktType = rtcp_parse_next_item(parser);
    if (pktType == RTCPPacketTypes::kPsfbRemb) {
        pktType = rtcp_parse_next_item(parser);
        if (pktType == RTCPPacketTypes::kPsfbRembItem) {
            strlen += snprintf(strbuf+strlen, strbufsz-strlen, "-[name=%s,sendssrc=%u,mediassrc=%u,br=%u,ssrcs=%u]", "PSFB_APP_REMB"
                               , sender_ssrc, media_ssrc
                               , body->REMBItem.BitRate, body->REMBItem.NumberOfSSRCs);
            rtc_dbgi("%s", strbuf);
            for(uint8_t ui = 0; ui < body->REMBItem.NumberOfSSRCs; ui++){
                rtc_dbgi("    - ssrc[%u]=%u", ui, body->REMBItem.SSRCs[ui]);
            }
            return rtcp_parse_next_packet(parser);
        } else if (pktType == RTCPPacketTypes::kPsfbEsmbItem) {
            strlen += snprintf(strbuf+strlen, strbufsz-strlen, "-[name=%s,sendssrc=%u,mediassrc=%u,br=%u,ssrc=%u]", "PSFB_APP_ESMB"
                    , sender_ssrc, media_ssrc
                    , body->ESMBItem.BitRateMax, body->ESMBItem.SSRC);
            rtc_dbgi("%s", strbuf);
            return rtcp_parse_next_packet(parser);
        }
    }
    strlen += snprintf(strbuf+strlen, strbufsz-strlen, "-[name=%s,sendssrc=%u,mediassrc=%u]", "unknown-rtcp-remb", sender_ssrc, media_ssrc);
    rtc_dbgi("%s", strbuf);
    return rtcp_parse_next_packet(parser);

}

static inline
int dump_BYE(rtc_endpoint_t rtc
        , rtcp_parser_t parser, const RtcpCommonHeader * header, const RTCPPacket * body
        , const char * name
        , char * strbuf, int& strbufsz, int& strlen ){
    strlen += snprintf(strbuf+strlen, strbufsz-strlen, "-[name=%s,sendssrc=%u]", name, body->BYE.SenderSSRC);
    rtc_dbgi("%s", strbuf);
    return rtcp_parse_next_item(parser);
}

void dump_rtcp(rtc_endpoint_t rtc, const char * prefix, void* buf_, int len) {
    rtc_dbgi("%s rtcp packets, len=%d", prefix, len);
    
    rtcp_parser_t parser = rtc->rtcp_parser;
    int pkttype = rtcp_parse_first(parser, (const unsigned char *)buf_, len);
    const RtcpCommonHeader * header = rtcp_get_header(parser);
    const RTCPPacket * body = rtcp_get_body(parser);
    
    int pno = 0;
    char strbuf[512];
    int strbufsz = sizeof(strbuf);
    while( pkttype  != kInvalid){
        ++pno;
        int strlen = 0;
        strlen += snprintf(strbuf+strlen, strbufsz-strlen, "  rtcp[%d]-[v=%u,rc=%u,pt=%u,plen=%u]"
                           , pno, header->version, header->count_or_format, header->packet_type, header->payload_size_bytes);
        switch(pkttype){
            case kRr:
                pkttype = dump_RR(rtc, parser, header, body, "RR", strbuf, strbufsz, strlen);
                break;
            case kSr:
                pkttype = dump_SR(rtc, parser, header, body, "SR", strbuf, strbufsz, strlen);
                break;
            case kSdes:
                pkttype = dump_SDES(rtc, parser, header, body, "SDES", strbuf, strbufsz, strlen);
                break;
            case kRtpfbNack:
                pkttype = dump_NACK(rtc, parser, header, body, "RTPFB-NACK", strbuf, strbufsz, strlen);
                break;
            case kPsfbPli:
                pkttype = dump_PSFB_PLI(rtc, parser, header, body, "PSFB-PLI", strbuf, strbufsz, strlen);
                break;
            case kPsfbApp:
                pkttype = dump_PSFB_APP(rtc, parser, header, body, "PSFB-APP", strbuf, strbufsz, strlen);
                break;
            case kBye:
                pkttype = dump_BYE(rtc, parser, header, body, "BYE", strbuf, strbufsz, strlen);
                break;
            case kTransportFeedback:
            {
                //strlen += snprintf(strbuf+strlen, strbufsz-strlen, "-[name=%s,pkttype=%d]", "TransportFeedback" , pkttype);
                auto fb = rtcp_get_transport_feedback(parser);
                strlen += snprintf(strbuf+strlen, strbufsz-strlen, "-[name=%s,fb-seq=%u,sendssrc=%u,mediassrc=%u,base-seq=%u,base-time=%ld,size=%lu]", "TRANSPORT-FEEDBACK"
                        , fb->fb_seq, fb->ssrc_sender, fb->ssrc_media, fb->base_seq, fb->base_time, fb->deltas.size());
                rtc_dbgi("%s", strbuf);
                pkttype = rtcp_parse_next_packet(parser);
            }   break;
            default:
                strlen += snprintf(strbuf+strlen, strbufsz-strlen, "-[name=%s,pkttype=%d]", "unknown-rtcp" , pkttype);
                rtc_dbgi("%s", strbuf);
                pkttype = rtcp_parse_next_packet(parser);
                break;
        }
    }
    
}

// Check the RTP payload type.  If 63 < payload type < 96, it's RTCP.
// For additional details, see http://tools.ietf.org/html/rfc5761.
static int check_is_rtcp(const unsigned char* data, int len) {
    if (len < 2) {
        return 0;
    }
    char pt = data[1] & 0x7F;
    return (63 < pt) && (pt < 96);
}




static
uint32_t rtcp_get_sender_ssrc(const unsigned char *packet, int len) {
    if(packet == NULL || len == 0){
        return 0;
    }
    
    const unsigned char * rtcp = packet;
    int pno = 0, total = len;
    while(rtcp) {
        unsigned char v =  (rtcp[0]>>6) & 0x3;
//        unsigned char p =  (rtcp[0]>>5) & 0x1;
//        unsigned char rc = (rtcp[0]>>0) & 0x1F;
        unsigned char pt = (rtcp[1]>>0) & 0xFF;
        unsigned short plen = be_get_u16(rtcp+2); // packet length in 4bytes minus 1
        
        if(v != 2){
            return 0;
        }
        
        pno++;
        if(pt == RTCP_SR || pt == RTCP_RR || pt == RTCP_RTPFB || pt == RTCP_PSFB){
            unsigned int ssrc = be_get_u32(rtcp+4);
            return ssrc;
        }
        
        if(plen == 0) {
//            break;
        }
        total -= plen*4+4;
        if(total <= 0) {
            break;
        }
        rtcp = (const unsigned char *)((uint32_t*)rtcp + plen + 1);
    }
    return 0;
}

static
uint32_t rtcp_change_sender_ssrc(unsigned char *packet, int len, uint32_t local_primary_ssrc, uint32_t local_rtx_ssrc,
                                 uint32_t remote_primary_ssrc, uint32_t remote_rtx_ssrc,
                                 uint32_t origin_local_primary_ssrc, uint32_t origin_local_rtx_ssrc,
                                 uint32_t origin_remote_primary_ssrc, uint32_t origin_remote_rtx_ssrc) {
    if(packet == NULL || len == 0){
        return 0;
    }
    
    unsigned char * rtcp = packet;
    int pno = 0, total = len;
    while(rtcp) {
        unsigned char v =  (rtcp[0]>>6) & 0x3;
        //        unsigned char p =  (rtcp[0]>>5) & 0x1;
                unsigned char rc = (rtcp[0]>>0) & 0x1F;
        unsigned char pt = (rtcp[1]>>0) & 0xFF;
        unsigned short plen = be_get_u16(rtcp+2); // packet length in 4bytes minus 1
        
        if(v != 2){
            return 0;
        }
        
        pno++;
        
        if(pt == RTCP_RTPFB || pt == RTCP_PSFB){
            uint32_t sender_ssrc = be_get_u32(rtcp + 4);
            if (sender_ssrc == origin_remote_primary_ssrc) {
                be_set_u32(local_primary_ssrc, rtcp + 4);
            } else if (sender_ssrc == origin_remote_rtx_ssrc){
                be_set_u32(local_rtx_ssrc, rtcp + 4);
            }

            if(plen >= 2){
                uint32_t media_ssrc = be_get_u32(rtcp + 8);
                if (media_ssrc == origin_local_primary_ssrc) {
                    be_set_u32(remote_primary_ssrc, rtcp + 8);
                } else if (media_ssrc == origin_local_rtx_ssrc) {
                    be_set_u32(remote_rtx_ssrc, rtcp + 8);
                }
            }
            
        }else if(pt == RTCP_FIR || pt == RTCP_NACK || pt == RTCP_SR || pt == RTCP_RR){
//            unsigned int ssrc = be_get_u32(rtcp+4);
//            return ssrc;
            uint32_t sender_ssrc = be_get_u32(rtcp + 4);
            if (sender_ssrc == origin_remote_primary_ssrc) {
                be_set_u32(local_primary_ssrc, rtcp + 4);
            } else if (sender_ssrc == origin_remote_rtx_ssrc){
                be_set_u32(local_rtx_ssrc, rtcp + 4);
            }

            if(pt == RTCP_SR){
//                gdbgi("SR: plen=%d, rc=%d", plen, rc);
                for (unsigned char uc = 0; uc < rc; uc++) {
                    uint32_t sender_ssrc = be_get_u32(rtcp + 28 + uc * 24);
                    if (sender_ssrc == origin_remote_primary_ssrc) {
                        be_set_u32(local_primary_ssrc, rtcp + 28 + uc * 24);
                    } else if (sender_ssrc == origin_remote_rtx_ssrc){
                        be_set_u32(local_rtx_ssrc, rtcp + 28 + uc * 24);
                    }
                }
            }else if(pt == RTCP_RR){
//                gdbgi("RR: plen=%d, rc=%d", plen, rc);
                for (unsigned char uc = 0; uc < rc; uc++) {
                    uint32_t receiver_ssrc = be_get_u32(rtcp + 8 + uc * 24);
                    if (receiver_ssrc == origin_local_primary_ssrc) {
                        be_set_u32(remote_primary_ssrc, rtcp + 8 + uc * 24);
                    } else if (receiver_ssrc == origin_local_rtx_ssrc) {
                        be_set_u32(remote_rtx_ssrc, rtcp + 8 + uc * 24);
                    }
                }
            }
        } else if (pt == RTCP_SDES) {
            uint8_t sc = rtcp[0] & 0x1Fu;
            uint8_t* chunk = rtcp + 4;
            while (sc) {
                uint32_t ssrc = be_get_u32(chunk);
                if (ssrc == origin_remote_primary_ssrc) {
                    be_set_u32(local_primary_ssrc, chunk);
                } else if (ssrc == origin_remote_rtx_ssrc) {
                    be_set_u32(local_rtx_ssrc, chunk);
                }

                // CNAME item is untouched since rewriting text need re-alloc a new packet,
                // todo: let's rewrite SDES once we are ready to construct a completely new RTCP packet from this
                char* chunk_end = (char *) chunk + 4 + 4;
                while (*chunk_end != 0) {
                    chunk_end += 4;
                }
                --sc;
            }
        }
        
        if(plen == 0) {
            break;
        }
        total -= plen*4+4;
        if(total <= 0) {
            break;
        }
        rtcp = (unsigned char *)((uint32_t*)rtcp + plen + 1);
    }
    return 0;
}

// uint32_t origin_local_primary_ssrc,
// uint32_t origin_local_rtx_ssrc,
// uint32_t origin_remote_primary_ssrc,
// uint32_t origin_remote_rtx_ssrc
static
void replace_rtcp_ssrc_to_placeholder(unsigned char *packet, int len, placeholder::stream placeholder, rtc_nice_transport* xtrans) {
    if(packet == NULL){
        return;
    }

    unsigned char * rtcp = packet;
    int pno = 0, total = len;
    while(rtcp) {
        unsigned char v =  (rtcp[0]>>6) & 0x3;
        unsigned char rc = (rtcp[0]>>0) & 0x1F;
        unsigned char pt = (rtcp[1]>>0) & 0xFF;
        unsigned short plen = be_get_u16(rtcp+2); // packet length in 4bytes minus 1, e.g. 1 is 8 bytes, 2 is 12 bytes

        if(v != 2){
            return;
        }

        pno++;

        if(pt == RTCP_RTPFB || pt == RTCP_PSFB){
            uint32_t sender_ssrc = be_get_u32(rtcp + 4);
            if (sender_ssrc == xtrans->remote_sources[0].src_id) {                  // origin_remote_primary_ssrc
                be_set_u32(placeholder.remote.pri, rtcp + 4);                       // placeholder::rtcp_remote_primary_ssrc
            } else if (sender_ssrc == xtrans->remote_sources[1].src_id) {           // origin_remote_rtx_ssrc
                be_set_u32(placeholder.remote.rtx, rtcp + 4);                       // placeholder::rtcp_remote_rtx_ssrc
            }

            if(plen >= 2){
                uint32_t media_ssrc = be_get_u32(rtcp + 8);
                if (media_ssrc == xtrans->local_sources[0].src_id) {                // origin_local_primary_ssrc
                    be_set_u32(placeholder.local.pri, rtcp + 8);                    // placeholder::rtcp_local_primary_ssrc
                } else if (media_ssrc == xtrans->local_sources[1].src_id) {         // origin_local_rtx_ssrc
                    be_set_u32(placeholder.local.rtx, rtcp + 8);                    // placeholder::rtcp_local_rtx_ssrc
                }
            }

        }else if(pt == RTCP_FIR || pt == RTCP_NACK || pt == RTCP_SR || pt == RTCP_RR){
//            unsigned int ssrc = be_get_u32(rtcp+4);
//            return ssrc;
            uint32_t sender_ssrc = be_get_u32(rtcp + 4);
            if (sender_ssrc == xtrans->remote_sources[0].src_id) {                  // origin_remote_primary_ssrc
                be_set_u32(placeholder.remote.pri, rtcp + 4);                       // placeholder::rtcp_remote_primary_ssrc
            } else if (sender_ssrc == xtrans->remote_sources[1].src_id) {           // origin_remote_rtx_ssrc
                be_set_u32(placeholder.remote.rtx, rtcp + 4);                       // placeholder::rtcp_remote_rtx_ssrc
            }

            if(pt == RTCP_SR){
//                gdbgi("SR: plen=%d, rc=%d", plen, rc);
                for (unsigned char uc = 0; uc < rc; uc++) {
                    uint32_t sender_ssrc = be_get_u32(rtcp + 28 + uc * 24);
                    if (sender_ssrc == xtrans->remote_sources[0].src_id) {          // origin_remote_primary_ssrc
                        be_set_u32(placeholder.remote.pri, rtcp + 28 + uc * 24);    // placeholder::rtcp_remote_primary_ssrc
                    } else if (sender_ssrc == xtrans->remote_sources[1].src_id) {   // origin_remote_rtx_ssrc
                        be_set_u32(placeholder.remote.rtx, rtcp + 28 + uc * 24);    // placeholder::rtcp_remote_rtx_ssrc
                    }
                }
            }else if(pt == RTCP_RR){
//                gdbgi("RR: plen=%d, rc=%d", plen, rc);
                for (unsigned char uc = 0; uc < rc; uc++) {
                    uint32_t receiver_ssrc = be_get_u32(rtcp + 8 + uc * 24);
                    if (receiver_ssrc == xtrans->local_sources[0].src_id) {         // origin_local_primary_ssrc
                        be_set_u32(placeholder.local.pri, rtcp + 8 + uc * 24);      // placeholder::rtcp_local_primary_ssrc
                    } else if (receiver_ssrc == xtrans->local_sources[1].src_id) {  // origin_local_rtx_ssrc
                        be_set_u32(placeholder.local.rtx, rtcp + 8 + uc * 24);      // placeholder::rtcp_local_rtx_ssrc
                    }
                }
            }
        } else if (pt == RTCP_SDES) {
            uint8_t sc = rtcp[0] & 0x1Fu;
            uint8_t* chunk = rtcp + 4;
            while (sc) {
                uint32_t ssrc = be_get_u32(chunk);
                if (ssrc == xtrans->remote_sources[0].src_id) {                     // origin_remote_primary_ssrc
                    be_set_u32(placeholder.remote.pri, chunk);                      // placeholder::rtcp_remote_primary_ssrc
                } else if (ssrc == xtrans->remote_sources[1].src_id) {              // origin_remote_rtx_ssrc
                    be_set_u32(placeholder.remote.rtx, chunk);                      // placeholder::rtcp_remote_rtx_ssrc
                }

                // CNAME item is untouched since rewriting text need re-alloc a new packet,
                // todo: let's rewrite SDES once we are ready to construct a completely new RTCP packet from this
                char* chunk_end = (char *) chunk + 4 + 4;
                while (*chunk_end != 0) {
                    chunk_end += 4;
                }
                --sc;
            }
        }

        if(plen == 0) {
            break;
        }
        total -= plen*4+4;
        if(total <= 0) {
            break;
        }
        rtcp = (unsigned char *)((uint32_t*)rtcp + plen + 1);
    }
    return;
}

// uint32_t actual_local_primary_ssrc,
// uint32_t actual_local_rtx_ssrc,
// uint32_t actual_remote_primary_ssrc,
// uint32_t actual_remote_rtx_ssrc

static
void replace_rtcp_placeholder_to_ssrc(unsigned char *packet, int len, placeholder::stream placeholder, rtc_nice_transport* xtrans) {
    if(packet == NULL){
        return;
    }

    unsigned char * rtcp = packet;
    int total = len;
    while(rtcp) {
        unsigned char v =  (rtcp[0]>>6) & 0x3;
        unsigned char rc = (rtcp[0]>>0) & 0x1F;
        unsigned char pt = (rtcp[1]>>0) & 0xFF;
        unsigned short plen = be_get_u16(rtcp+2); // packet length in 4bytes minus 1, e.g. 1 is 8 bytes, 2 is 12 bytes

        if(v != 2){
            return;
        }

        if(pt == RTCP_RTPFB || pt == RTCP_PSFB){
            uint32_t sender_ssrc = be_get_u32(rtcp + 4);
            if (sender_ssrc == placeholder.remote.pri) {                    // placeholder::rtcp_remote_primary_ssrc
                be_set_u32(xtrans->local_sources[0].src_id, rtcp + 4);      // actual_local_primary_ssrc
            } else if (sender_ssrc == placeholder.remote.rtx){              // placeholder::rtcp_remote_rtx_ssrc
                be_set_u32(xtrans->local_sources[1].src_id, rtcp + 4);      // actual_local_rtx_ssrc
            }

            if(plen >= 2){
                uint32_t media_ssrc = be_get_u32(rtcp + 8);
                if (media_ssrc == placeholder.local.pri) {                  // placeholder::rtcp_local_primary_ssrc
                    be_set_u32(xtrans->remote_sources[0].src_id, rtcp + 8); // actual_remote_primary_ssrc
                } else if (media_ssrc == placeholder.local.rtx) {           // placeholder::rtcp_local_rtx_ssrc
                    be_set_u32(xtrans->remote_sources[1].src_id, rtcp + 8); // actual_remote_rtx_ssrc
                }
            }

        }else if(pt == RTCP_FIR || pt == RTCP_NACK || pt == RTCP_SR || pt == RTCP_RR){
//            unsigned int ssrc = be_get_u32(rtcp+4);
//            return ssrc;
            uint32_t sender_ssrc = be_get_u32(rtcp + 4);
            if (sender_ssrc == placeholder.remote.pri) {                    // placeholder::rtcp_remote_primary_ssrc
                be_set_u32(xtrans->local_sources[0].src_id, rtcp + 4);      // actual_local_primary_ssrc
            } else if (sender_ssrc == placeholder.remote.rtx) {             // placeholder::rtcp_remote_rtx_ssrc
                be_set_u32(xtrans->local_sources[1].src_id, rtcp + 4);      // actual_local_rtx_ssrc
            }

            if(pt == RTCP_SR){
//                gdbgi("SR: plen=%d, rc=%d", plen, rc);
                for (unsigned char uc = 0; uc < rc; uc++) {
                    uint32_t sender_ssrc = be_get_u32(rtcp + 28 + uc * 24);
                    if (sender_ssrc == placeholder.remote.pri) {                            // placeholder::rtcp_remote_primary_ssrc
                        be_set_u32(xtrans->local_sources[0].src_id, rtcp + 28 + uc * 24);   // actual_local_primary_ssrc
                    } else if (sender_ssrc == placeholder.remote.rtx) {                     // placeholder::rtcp_remote_rtx_ssrc
                        be_set_u32(xtrans->local_sources[1].src_id, rtcp + 28 + uc * 24);   // actual_local_rtx_ssrc
                    }
                }
            }else if(pt == RTCP_RR){
//                gdbgi("RR: plen=%d, rc=%d", plen, rc);
                for (unsigned char uc = 0; uc < rc; uc++) {
                    uint32_t receiver_ssrc = be_get_u32(rtcp + 8 + uc * 24);
                    if (receiver_ssrc == placeholder.local.pri) {                           // placeholder::rtcp_local_primary_ssrc
                        be_set_u32(xtrans->remote_sources[0].src_id, rtcp + 8 + uc * 24);   // actual_remote_primary_ssrc
                    } else if (receiver_ssrc == placeholder.local.rtx) {                    // placeholder::rtcp_local_rtx_ssrc
                        be_set_u32(xtrans->remote_sources[1].src_id, rtcp + 8 + uc * 24);   // actual_remote_rtx_ssrc
                    }
                }
            }
        } else if (pt == RTCP_SDES) {
            uint8_t sc = rtcp[0] & 0x1Fu;
            uint8_t* chunk = rtcp + 4;
            while (sc) {
                uint32_t ssrc = be_get_u32(chunk);
                if (ssrc == placeholder.remote.pri) {                   // placeholder::rtcp_remote_primary_ssrc
                    be_set_u32(xtrans->local_sources[0].src_id, chunk); // actual_local_primary_ssrc
                } else if (ssrc == placeholder.remote.rtx) {            // placeholder::rtcp_remote_rtx_ssrc
                    be_set_u32(xtrans->local_sources[1].src_id, chunk); // actual_local_rtx_ssrc
                }

                // CNAME item is untouched since rewriting text need re-alloc a new packet,
                // todo: let's rewrite SDES once we are ready to construct a completely new RTCP packet from this
                char* chunk_end = (char *) chunk + 4 + 4;
                while (*chunk_end != 0) {
                    chunk_end += 4;
                }
                --sc;
            }
        }

        if(plen == 0) {
            break;
        }
        total -= plen*4+4;
        if(total <= 0) {
            break;
        }
        rtcp = (unsigned char *)((uint32_t*)rtcp + plen + 1);
    }
}

static
void rtc_endpoint_call_listeners_stream_data(rtc_endpoint_t rtc, int stream_type, rtc_codec_id_t codec, rtc_codec_id_t fec_codec, int data_type, int is_rtcp, int is_rtx, unsigned char * buf, int len){
    if(!rtc->listeners) return;
    GList * list = rtc->listeners->head;
//	  rtc_dbgi("rtc_endpoint_call_listeners_stream_data ",data_type,len,stream_type,is_rtcp,is_rtx);

    while (list)
    {
        GList *next = list->next;
        rtc_endpoint_callbacks * listener = (rtc_endpoint_callbacks *)list->data;
        if(listener->on_stream_data){
            bool handled = listener->on_stream_data(rtc, listener->context, stream_type, codec, fec_codec, data_type, is_rtcp, is_rtx, buf, len);
            if (handled) {
                break;
            }
        }
        
        list = next;
    }
}

static
void rtc_endpoint_call_listeners_event(rtc_endpoint_t rtc, int event, void * event_arg1, int event_arg2){
    if(!rtc->listeners) return;
    GList * list = rtc->listeners->head;
    while (list)
    {
        GList *next = list->next;
        rtc_endpoint_callbacks * listener = (rtc_endpoint_callbacks *)list->data;
        if(listener->on_event){
            listener->on_event(rtc, listener->context, event, event_arg1, event_arg2);
        }
        
        list = next;
    }
    
}

static
void rtc_endpoint_call_listeners_destroy(rtc_endpoint_t rtc){
    if(!rtc->listeners) return;
    GList * list = rtc->listeners->head;
    while (list)
    {
        GList *next = list->next;
        rtc_endpoint_callbacks * listener = (rtc_endpoint_callbacks *)list->data;
        if(listener->on_destroy){
            listener->on_destroy(rtc, listener->context);
        }
        
        list = next;
    }
    
}





//static inline
//rtc_nice_transport *  rtc_endpoint_xtrans(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans){
//    rtc_nice_transport * xtrans = NULL;
//    if(local_xtrans == &rtc->xtranses[RTC_VIDEO] && rtc->video_xtrans){
//        xtrans = rtc->video_xtrans;
//    }else if(local_xtrans == &rtc->xtranses[RTC_AUDIO] &&rtc->audio_xtrans){
//        xtrans = rtc->audio_xtrans;
//    }
//    return xtrans;
//}
static inline
rtc_nice_transport *  rtc_endpoint_xtrans(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans){
    rtc_nice_transport * xtrans = NULL;
    if(rtc->destroy_xtranse_type != RTC_STREAM_TYPE_MAX){
        xtrans = &rtc->xtranses[rtc->destroy_xtranse_type == RTC_AUDIO ? RTC_VIDEO : RTC_AUDIO];
        return xtrans;
    }

    //不存在 bundle，或者 RTC_AUDIO 或 RTC_VIDEO只存在一个
    if(local_xtrans == &rtc->xtranses[RTC_VIDEO] && rtc->video_xtrans){
        xtrans = rtc->video_xtrans;
        return xtrans;
    }
    if(local_xtrans == &rtc->xtranses[RTC_AUDIO] && rtc->audio_xtrans){
        xtrans = rtc->audio_xtrans;
        return xtrans;
    }
    return xtrans;
}

static inline
int get_use_xtrans(rtc_endpoint_t rtc, int stream_type, rtc_nice_transport* &local_xtrans, rtc_nice_transport* &xtrans){
    if(stream_type >= RTC_STREAM_TYPE_MAX){
        return -1;
    }

    local_xtrans = &rtc->xtranses[stream_type];

    if(rtc->destroy_xtranse_type != RTC_STREAM_TYPE_MAX){
        //destroy_xtranse_type在create时 RTC_STREAM_TYPE_MAX
        //仅在 rtc_endpoint_bundle_transports 赋值为VIDEO AUDIO
        //当 rtc_endpoint_bundle_transports VIDEO 或 AUDIO时，存在Bundle 而且 audio_xtrans video_xtrans同时存在
        //而且 audio_xtrans 与 video_xtrans 相等

        xtrans = &rtc->xtranses[rtc->destroy_xtranse_type == RTC_AUDIO ? RTC_VIDEO : RTC_AUDIO];
        return 0;
    }

    //不存在 bundle，或者 RTC_AUDIO 或 RTC_VIDEO只存在一个
    if(local_xtrans == &rtc->xtranses[RTC_VIDEO] && rtc->video_xtrans){
        xtrans = rtc->video_xtrans;
        return 0;
    }
    if(local_xtrans == &rtc->xtranses[RTC_AUDIO] && rtc->audio_xtrans){
        xtrans = rtc->audio_xtrans;
        return 0;
    }

    return -1;
}


static inline
uint32_t translate_seq(rtc_nice_transport * local_xtrans, uint32_t seq){
    if(!local_xtrans->is_orgin_seq_valid){
        local_xtrans->orgin_rtp_seq_base = seq;
        local_xtrans->is_orgin_seq_valid = 1;
    }
    seq = local_xtrans->sent_rtp_seq_base + (seq - local_xtrans->orgin_rtp_seq_base);
    local_xtrans->last_sent_rtp_seq = seq;
    return seq;
}

static inline
uint32_t translate_inverse_seq(rtc_nice_transport * local_xtrans, uint32_t seq){
//    return seq - local_xtrans->sent_rtp_seq_base + local_xtrans->orgin_rtp_seq_base;
    if(local_xtrans->is_orgin_seq_valid){
        return seq - local_xtrans->sent_rtp_seq_base + local_xtrans->orgin_rtp_seq_base;
    }else{
        return seq;
    }
}


typedef int (*nack_processor_t) (rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans, rtc_nice_transport * xtrans
        , uint16_t orig_pid, uint16_t orig_blp
        , uint16_t * pnew_pid, uint16_t * pnew_blp);

static
int rtc_endpoint_filter_nacks(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans, rtc_nice_transport * xtrans
                              , const uint8_t * rtcp, int len_in_32bit
                              , uint8_t * new_rtcp, int * p_new_rtcp_len
                              , nack_processor_t nack_cb){
    

    int offset = 0;
    memcpy(new_rtcp+offset, rtcp, 12); // copy origin rtcp header + ssrc + media ssrc
    offset += 12;
    
    //            https://tools.ietf.org/html/rfc4585#page-34
    //            0                   1                   2                   3
    //            0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    //            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //            |            PID                |             BLP               |
    //            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //
    int nacks = len_in_32bit - 2; // skip ssrc and media ssrc
    for(int n = 0; n < nacks; n++){
        const unsigned char * p = rtcp + 12 + n*4;
        uint16_t orig_pid = be_get_u16(p);
        uint16_t orig_blp = be_get_u16(p+2);
//        rtc_dbgi("filter nack: pid=%u, blp=0x%02X", orig_pid, orig_blp);
        
        uint16_t new_pid = 0; // new_pid must 32-bit type
        uint16_t new_blp = 0;
        nack_cb(rtc, local_xtrans, xtrans, orig_pid, orig_blp, &new_pid, & new_blp);
        
        
        if(new_pid != 0 || new_blp != 0){
//            new_blp >>= (new_pid - orig_pid);
//            new_pid &= 0x0000FFFF;
            
            
            if(new_pid != orig_pid || new_blp != orig_blp){
                nack_dbgi("filter nack: orig_pid=%u, orig_blp=0x%02X, new_pid=%u, new_blp=0x%02X", orig_pid, orig_blp, new_pid, new_blp);
            }
            
            be_set_u16(new_pid, new_rtcp+offset);
            be_set_u16(new_blp, new_rtcp+offset+2);
        }
        offset += 4;
//        else if(nacks == 1){
//            rtc_dbgi("dddddd-222");
//        }

    }
    
    if(offset > 12){
        unsigned short w = offset/4 - 1;
        be_set_u16(w, new_rtcp+2);
        *p_new_rtcp_len = offset;
    }else{
        *p_new_rtcp_len = 0;
    }
    
    return 0;
    
}



static
unsigned char * rtc_endpoint_filter_rtcp(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans, void* rtcp_pkts, int * prtcp_pktslen
                                         , nack_processor_t nack_cb, unsigned char * new_rtcps){
    
    rtc_nice_transport * xtrans = rtc_endpoint_xtrans(rtc, local_xtrans);
    if(!xtrans){
        return NULL;
    }
    
//    unsigned char * new_rtcps = rtc->work_udp_buf;
    int new_rtcps_len = 0;
    
    const unsigned char * rtcp = (const unsigned char *)rtcp_pkts;
    int pno = 0, total = *prtcp_pktslen;
    while(rtcp) {
        unsigned char v =  (rtcp[0]>>6) & 0x3;
        if(v != 2){
            rtc_dbge("wrong rtcp v %02X", v);
            return NULL;
        }
        //        unsigned char p =  (rtcp[0]>>5) & 0x1;
        unsigned char rc = (rtcp[0]>>0) & 0x1F;
        unsigned char pt = (rtcp[1]>>0) & 0xFF;
        unsigned short plen = be_get_u16(rtcp+2); // packet length in 4bytes minus 1
        //        unsigned int ssrc = be_get_u32(rtcp+4);
        if ((plen + 1) * 4 > total) {
            rtc_dbge("invalid rtcp length %u", plen);
            return NULL;
        }
        pno++;
        if (rtc->is_dumping_rtcp) {
            rtc_dbgi("filter rtcp=%p, pno=%d, rc=%d, pt=%d, plen=%d", rtcp, pno, rc, pt, plen);
        }
        if(pt == RTCP_RTPFB && rc == 1 && plen >= 2){
            int new_len = 0;
            rtc_endpoint_filter_nacks( rtc, local_xtrans, xtrans
                                      , rtcp, plen
                                      , new_rtcps+new_rtcps_len, &new_len
                                      , nack_cb);
            if(new_len > 0){
                new_rtcps_len += new_len;
            }
        }else{
            memcpy(new_rtcps+new_rtcps_len, rtcp, plen*4+4);
            new_rtcps_len += (plen*4+4);
        }
        
        
        if(plen == 0) {
            break;
        }
        total -= plen*4+4;
        if(total <= 0) {
            break;
        }
        rtcp = (const unsigned char *)((uint32_t*)rtcp + plen + 1);
    }
    
    *prtcp_pktslen = new_rtcps_len;
    return new_rtcps;
}

static inline
int on_recv_nack(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans, rtc_nice_transport * xtrans
                 , uint16_t orig_pid, uint16_t orig_blp
                 , uint16_t * pnew_pid, uint16_t * pnew_blp){
    unsigned char *bufs[17];
    int lengths[17];
    int codecs[17];
    int num = 0;
    uint32_t skip_num = 0;
    int64_t now = get_timestamp_ms();
    if (rtc->is_dumping_rtcp) {
        nack_dbgi("on_recv_nack: pid=%u, blp=0x%02X", orig_pid, orig_blp);
    }
    if (local_xtrans->sent_cache) {
        xmcache_pull_rtp_range(local_xtrans->sent_cache, now, now
                , orig_pid, orig_blp
                , pnew_pid, pnew_blp
                , &skip_num
                , bufs, lengths, codecs, &num);
        if((*pnew_pid != 0 || *pnew_blp != 0)
           && (*pnew_pid != orig_pid || *pnew_blp != orig_blp)){
            if (rtc->is_dumping_rtcp) {
                nack_dbgi("on_recv_nack: new_pid=%u, new_blp=0x%02X", *pnew_pid,
                        *pnew_blp);
            }
        }

        for(int n = 0; n < num; n++){
            if (rtc->is_dumping_rtcp) {
                nack_dbgi("on_recv_nack: resend rtp seq=%u, len=%d",
                        be_get_u16(bufs[n] + 2), lengths[n]);
            }
            int is_rtcp = RTC_RTP;
            memcpy(rtc->send_work_buf, bufs[n], lengths[n]);
            int err = send_rtp_data(rtc, local_xtrans, xtrans, (rtc_codec_id_t)codecs[n], rtc_codec_id_unknown, rtc->send_work_buf, lengths[n], true);
            if (err) {
                rtc_dbge("on_recv_nack: send_rtp_data fail stream_id=%d, is_rtcp=%d, comp_id=%d", xtrans->stream_id, is_rtcp, xtrans->comps[is_rtcp].component_id);
                // we need the following translate_inverse_seq
            }

        }
    } else {
        *pnew_pid = orig_pid;
        *pnew_blp = orig_blp;
    }

    //if(*pnew_pid != 0 || *pnew_blp != 0){
        *pnew_pid = translate_inverse_seq(local_xtrans, *pnew_pid);
    //}

    return 0;
}

static
unsigned char * rtc_endpoint_filter_recv_rtcp(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans, void* rtcp_pkts, int * plen ){
//    if(!local_xtrans->sent_cache){
//        return (unsigned char *)rtcp_pkts;
//    }
    
    return rtc_endpoint_filter_rtcp( rtc,  local_xtrans,  rtcp_pkts,  plen, on_recv_nack, rtc->recv_work_buf);
}

// on_send_nack filter outgoing nack req by checking if seq was received in last 100ms
// since we have on_recv_nack, which checks if we have received such seq, the nack seq reach here should not have beed received
// so on_send_nack should be disabled
//static inline
//int on_send_nack(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans, rtc_nice_transport * xtrans
//                 , uint16_t orig_pid, uint16_t orig_blp
//                 , uint16_t * pnew_pid, uint16_t * pnew_blp){
//
//    int num = 0;
//    uint32_t skip_num = 0;
//    int64_t now = get_timestamp_ms();
//    nack_dbgi("on_send_nack: pid=%u, blp=0x%02X", orig_pid, orig_blp);
//    xmcache_req_range(local_xtrans->nack_req_cache, now, now-100
//                           , orig_pid, orig_blp
//                           , pnew_pid, pnew_blp
//                           , &skip_num
//                           , &num);
//    if((*pnew_pid != 0 || *pnew_blp != 0)
//       && (*pnew_pid != orig_pid || *pnew_blp != orig_blp)){
//        nack_dbgi("on_send_nack: new_pid=%u, new_blp=0x%02X", *pnew_pid, *pnew_blp);
//    }
//
////    if(orig_blp > 0) {
////        rtc_dbgi("dddddd");
////    }
//
//    return 0;
//}

static
unsigned char * rtc_endpoint_filter_rtcp_before_send(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans, void* rtcp_pkts, int * plen){
    //if(!local_xtrans->nack_req_cache){
        return (unsigned char *)rtcp_pkts;
    //}
    //return rtc_endpoint_filter_rtcp( rtc,  local_xtrans,  rtcp_pkts,  plen, on_send_nack, rtc->send_work_buf);
}



/**
 * 这个需要注意下：
 * 当sdp中ssrc时，remote_sources[].src_id为这个值；
 * 当sdp recvonly时，ssrc 不会含有，remote_sources[].src_id == 0；
 * 但是，sdp recvonly 如：sub rtc，他上行的rtcp包如rr，ssrc = 1
 * 所以 rtc_endpoint_get_video_transport_and_set_ssrc
 *
 * sdp sendrecv时
 * RR sub 发过来的
 * send ssrc == subrtc remote ssrc [video]，并被替换成placeholder
 * recv ssrc == subrtc local ssrc [video]，并被替换成placeholder
 *
 * sdp recvonly时，bundle audio video
 * send ssrc !== subrtc remote ssrc [video](这会是0，因为sdp中没有此值，此种情况下会取 video transport， 并把 transport remote ssrc 赋值成 send ssrc)
 * recv ssrc === subrtc remote ssrc [audio]; 此时不会被替换成placeholder
 *
 *
 * @param rtc
 * @param ssrc
 * @param pindex
 * @return
 */
static
rtc_nice_transport * rtc_endpoint_get_transport_by_remote_ssrc(rtc_endpoint_t rtc, uint32_t ssrc, int * pindex){
    for(int si = 0; si < 2; si++){
        rtc_nice_transport * xtrans = &rtc->xtranses[si];
        if(xtrans->enabled){
            for(int i = 0; i < 2; i++){
                if(ssrc == xtrans->remote_sources[i].src_id){
                    *pindex = i;
                    return xtrans;
                }
            }
        }
    }
    
    return NULL;
}

static
rtc_nice_transport * rtc_endpoint_get_video_transport_and_set_ssrc(rtc_endpoint_t rtc, uint32_t ssrc, int * pindex){
    rtc_nice_transport * tmp_xtrans = &rtc->xtranses[RTC_VIDEO];
    if(tmp_xtrans->enabled){
        for(int i = 0; i < 2; i++){
            if(tmp_xtrans->remote_sources[i].src_id == 0){
                tmp_xtrans->remote_sources[i].src_id = ssrc;
                *pindex = i;
                return tmp_xtrans;
            }
        }
    }
    return NULL;
}

static
rtc_nice_transport * rtc_endpoint_get_transport_by_remote_mlineindex(rtc_endpoint_t rtc, int mlineindex){
    if(!rtc->remote_sdpinfo) return NULL;
    if(rtc->remote_sdpinfo->audio_stream
       && mlineindex == rtc->remote_sdpinfo->audio_stream->mlineindex){
        if(rtc->xtranses[RTC_AUDIO].mlineindex >= 0){
            return &rtc->xtranses[RTC_AUDIO];
        }else{
            return NULL;
        }
        
    }
    
    if(rtc->remote_sdpinfo->video_stream
       && mlineindex == rtc->remote_sdpinfo->video_stream->mlineindex){
        if(rtc->xtranses[RTC_VIDEO].mlineindex >= 0){
            return &rtc->xtranses[RTC_VIDEO];
        }else{
            return NULL;
        }
        
    }
    
    return NULL;
}

int rtc_endpoint_write_tlv_header(rtc_endpoint_t rtc, tlv_file_t tlv){
    
    int ret = 0;
    do{
        ret = 0;
        uint8_t buf[128];
        for (auto codec : rtc->codecs->codecs) {
            auto codec_name = rtc_codec_name(codec->id);
            auto codec_name_len = strlen(codec_name) + 1;
            auto codec_type = rtc_codec_type(codec->id);
            be_set_u32(codec_type, buf + 0);
            be_set_u32(codec->pt,  buf + 4);
            be_set_u32(codec->clock_rate, buf + 8);
            be_set_u32(codec->channel_nb, buf + 12);
            be_set_u32(codec_name_len, buf + 16);
            memcpy(buf + 20, codec_name, codec_name_len);
            ret = tlv_file_write(tlv, TLV_TYPE_CODEC, 20 + codec_name_len, buf);
            if(ret){
                rtc_dbge("fail to write tlv-codec %s", codec_name);
                break;
            }
        }
        if (ret) {
            break;
        }
        if(rtc->remote_sdpinfo && rtc->remote_sdpinfo->sdpbuf_orig && rtc->remote_sdpinfo->sdp_len > 0){
            ret = tlv_file_write(tlv, TLV_TYPE_REMOTE_SDP, rtc->remote_sdpinfo->sdp_len, rtc->remote_sdpinfo->sdpbuf_orig);
            if(ret){
                rtc_dbge("fail to write tlv-sdp");
                break;
            }
        }
        ret = 0;
    }while(0);
    return ret;
}

int rtc_endpoint_write_tlv_tail(tlv_file_t tlv){
    tlv_file_write(tlv, TLV_TYPE_EOF, 0, NULL);
    return 0;
}


int rtc_endpoint_write_tlv_rtp_rtcp(tlv_file_t tlv, int is_rtcp, unsigned char * buf, int buf_len){
    if(tlv){
        int tlv_type = is_rtcp ? TLV_TYPE_RTCP : TLV_TYPE_RTP;
        int64_t ts = get_timestamp_ms();
        uint32_t ts_hi = (uint32_t)((ts>>32) & 0xFFFFFFFFUL);
        uint32_t ts_lo = (uint32_t)(ts & 0xFFFFFFFFUL);
        
        uint8_t ts_buf[8];
        be_set_u32(ts_hi, ts_buf+0);
        be_set_u32(ts_lo, ts_buf+4);
        return tlv_file_write2(tlv, tlv_type, 8, ts_buf, buf_len, buf);
    }
    return 0;
}

static inline
void rtc_endpoint_close_tlv(tlv_file_t *ptlv){
    
    if(*ptlv){
        rtc_endpoint_write_tlv_tail(*ptlv);
        tlv_file_close(*ptlv);
        *ptlv = NULL;
    }
}

static inline
int rtc_endpoint_open_tlv(rtc_endpoint_t rtc){
    if (rtc->force_record) {
        snprintf(rtc->filename, sizeof(rtc->filename), "/tmp/%s.tlv", rtc->obj_id);
    }

    
    if(rtc->filename[0] == '\0'){
        return -1;
    }
    
    if(rtc->tlv1){
        // already opened
        return 0;
    }

    char abs_path_name[5120] = {0,};

    sprintf(abs_path_name, "%s/%s", app_config_get()->tlv_path, rtc->filename);

    // int ret = 0;
    //do{

    std::string parent_dir(abs_path_name);

    auto last_separator = parent_dir.rfind("/");

    if (last_separator != std::string::npos) {
        parent_dir = parent_dir.substr(0, last_separator);
        mkdir_tree(parent_dir.c_str());
    }

    rtc->tlv1 = tlv_file_open(abs_path_name);

    if(!rtc->tlv1){
        rtc_dbge("fail to write-open tlv [%s]", abs_path_name);
        return -1;
    }

    rtc_dbgi("successfully open tlv [%s]", abs_path_name);

    return rtc_endpoint_write_tlv_header(rtc, rtc->tlv1);
}


static inline
int parse_rtp_bytes(unsigned char * buf, int slen){
    unsigned char p =  (buf[0]>>5) & 0x1;
    unsigned char x =  (buf[0]>>4) & 0x1;
    unsigned char cc = (buf[0]>>0) & 0xF;
    int header_len = 12 + cc * 4;
    if(x){
        int min = header_len + 4;
        if(min > slen) {
            return -1;
        }
        
        int ext_len = be_get_u16(buf+header_len+2);
        header_len += 4+ext_len*4;
    }
    int padding_len = 0;
    if(p){
        padding_len = buf[slen-1];
    }
    int payload_len = slen - header_len - padding_len;
    return payload_len;
}

// return +4 if added 4 bytes
static int filter_extensions(rtc_endpoint_t rtc, unsigned char* buf, int size) {
    uint16_t seq = be_get_u16(buf+2);
    int cc = buf[0] & 0xf;
    int x = buf[0] >> 4 & 1;

    if (x == 0) {
        return 0;
    }

    uint32_t ssrc = be_get_u32(buf+8);
    uint16_t ext_size = be_get_u16(buf+12+cc*4+2);
    uint8_t* ext_begin = buf+12+cc*4+4;
    uint8_t* ext_read = ext_begin;
    uint8_t* ext_write = ext_begin;
    uint8_t* ext_end = ext_begin + 4 * ext_size;

    if (ext_end > buf + size) {
        rtc_dbge("found invalid rtp extension, ext_size=%u, seq=%u", ext_size, seq);
        return 0;
    }

    // for now, we only translate one byte rtp header extension
    if ((buf[12+cc*4] == 0xBE && buf[12+cc*4+1] == 0xDE)) {
        while(ext_read < ext_end) {
            if (*ext_read != 0) {
                uint8_t id = ext_read[0] >> 4;
                uint8_t l = ext_read[0] & 0x0F;
                if (rtc->rtp_extens_video && (ssrc == rtc->xtranses[RTC_VIDEO].remote_sources[0].src_id
                        || ssrc == rtc->xtranses[RTC_VIDEO].remote_sources[1].src_id)) {
                    RtpExtensionType ext_type = rtc->rtp_extens_video->getType(id);
                    // we need to totally adapt the RtpPacket class to use RtpExtensions,
                    // that is a lot of coding to do, before that, we can simply
                    // keep abs-send-time and transport-sequence-number, erase others.
                    if (ext_type == RtpExtensionType::AbsSendTime && l == 2) {
                        //abs_send_time = be_get_u24(ext_read+1);
                        ext_write[0] = RtpExtensionType::AbsSendTime << 4 | 2;
                        ext_write[1] = ext_read[1];
                        ext_write[2] = ext_read[2];
                        ext_write[3] = ext_read[3];
                        ext_read += 4;
                        ext_write += 4;
                    } else if (ext_type == RtpExtensionType::TransportSequenceNumber && l == 1) {
                        //transport_seq = be_get_u16(ext_read+1);
                        ext_write[0] = RtpExtensionType::TransportSequenceNumber << 4 | 1;
                        ext_write[1] = ext_read[1];
                        ext_write[2] = ext_read[2];
                        ext_read += 3;
                        ext_write += 3;
                    } else {
                        ext_read += 1 + l + 1;
                    }
                } else if (rtc->rtp_extens_audio
                        && (ssrc == rtc->xtranses[RTC_AUDIO].remote_sources[0].src_id
                        || ssrc == rtc->xtranses[RTC_AUDIO].remote_sources[1].src_id)) {
                    RtpExtensionType ext_type = rtc->rtp_extens_audio->getType(id);
                    if (ext_type == RtpExtensionType::AbsSendTime && l == 2) {
                        ext_write[0] = RtpExtensionType::AbsSendTime << 4 | 2;
                        ext_write[1] = ext_read[1];
                        ext_write[2] = ext_read[2];
                        ext_write[3] = ext_read[3];
                        ext_read += 4;
                        ext_write += 4;
                    } else if (ext_type == RtpExtensionType::SSRCAudioLevel && l == 0) {
                        ext_write[0] = RtpExtensionType::SSRCAudioLevel << 4 | 0;
                        ext_write[1] = ext_read[1];
                        ext_read += 2;
                        ext_write += 2;
                    } else {
                        ext_read += 1 + l + 1;
                    }
                } else {
                    ext_read += 1;
                }
            } else {
                ext_read += 1;
            }
        }

    } else {
        ext_read = ext_end;
    }

    size_t written = ext_write - ext_begin;
    if (written == 0) {
        buf[0] &= 0xEF;
        ext_write -= 4; // 0xBE 0xDE and len
    } else {
        while (written % 4) {
            *ext_write++ = 0;
            written++;
        }
        be_set_u16(written / 4, buf + 14);
    }
    memmove(ext_write, ext_end, size - ext_size);
    return ext_write - ext_read;
}

static
bool simulate_drop(int is_rtcp){
    if(!is_rtcp){
        std::random_device rd;
        std::mt19937 mt(rd());
        bool drop_pkt = (mt() % 3) == 0;
        if(drop_pkt){
            return true;
        }
    }
    return false;
}

static
void process_recv_rtp_rtcp(rtc_endpoint_t rtc, rtc_nice_transport * xtrans, unsigned char * buf, int buf_len){
    int64_t recv_ts = get_timestamp_ms();
    xtrans->recv_bytes_count += buf_len;
    do{
        int is_rtcp = check_is_rtcp((unsigned char *)buf, buf_len);
        
        // drop_audio / drop_video
        if (!is_rtcp) {
            rtc_nice_transport* xtrans = nullptr;

            {
                uint32_t ssrc = be_get_u32(buf+8);
                int ssrc_index;
                xtrans = rtc_endpoint_get_transport_by_remote_ssrc(rtc, ssrc,
                        &ssrc_index);
            }

            if (xtrans && xtrans->stream_type == RTC_AUDIO && rtc->drop_audio) {
                return;
            } else if (xtrans && xtrans->stream_type == RTC_VIDEO && rtc->drop_video) {
                return;
            }
        }

        // count input byte
        if (is_rtcp) {
            uint32_t ssrc = rtcp_get_sender_ssrc(buf, buf_len);
            if (ssrc == rtc->xtranses[RTC_AUDIO].remote_sources[0].src_id || ssrc == rtc->xtranses[RTC_AUDIO].remote_sources[1].src_id) {
                rtc->xtranses[RTC_AUDIO].recv_rtcp_bytes += buf_len;
            } else if (ssrc == rtc->xtranses[RTC_VIDEO].remote_sources[0].src_id || ssrc == rtc->xtranses[RTC_VIDEO].remote_sources[1].src_id){
                rtc->xtranses[RTC_VIDEO].recv_rtcp_bytes += buf_len;
            } else {
                xtrans->recv_unknown_bytes += buf_len;
            }
        } else {
            uint32_t ssrc = be_get_u32(buf+8);
            if (ssrc == rtc->xtranses[RTC_AUDIO].remote_sources[0].src_id || ssrc == rtc->xtranses[RTC_AUDIO].remote_sources[1].src_id) {
                rtc->xtranses[RTC_AUDIO].recv_rtp_bytes += buf_len;
            } else if (ssrc == rtc->xtranses[RTC_VIDEO].remote_sources[0].src_id || ssrc == rtc->xtranses[RTC_VIDEO].remote_sources[1].src_id){
                rtc->xtranses[RTC_VIDEO].recv_rtp_bytes += buf_len;
            } else {
                xtrans->recv_unknown_bytes += buf_len;
            }
        }

        bool is_rtx = false;
        unsigned int ssrc;
        int slen;
        rtc_codec_id_t codec_id = rtc_codec_id_unknown;
        rtc_codec_id_t fec_codec_id = rtc_codec_id_unknown;

        if(!is_rtcp){
            if (rtc->is_dumping_rtp) {
                dump_rtp(rtc, "recv protected", buf, buf_len);
            }
            slen = xdtls_srtp_unprotect(xtrans->dtls, (unsigned char *)buf, buf_len);
            if(slen <= 0) break;
            
            if(rtc->tlv_with_raw_packet){
                rtc_endpoint_write_tlv_rtp_rtcp(rtc->tlv1, is_rtcp, buf, slen);
            }

            
            ssrc = be_get_u32(buf+8);
            uint8_t pt = buf[1] & 0x7F;
            rtc_codec_t* codec = rtc_codecs_find_by_pt(rtc->codecs, pt);
            if (!codec) {
                codec = rtc_codecs_find_by_rtx_pt(rtc->codecs, pt);
                if (!codec) {
                    rtc_dbge("recv unknown payload type ssrc=%u, pt=%d", ssrc, pt);
                    break;
                } else {
                    is_rtx = true;
                }
            }
            codec_id = codec->id;

            uint8_t x = buf[0] >> 4 & 0x1u;
            uint8_t cc = buf[0] & 0x0f;

            int hdr_size = 12;
            int csrc_size = 0;
            if (cc) {
                csrc_size = cc * 4;
            }

            int ext_size = 0;
            if (x) {
                ext_size = (be_get_u16(buf+hdr_size+csrc_size+2) + 1) * 4;
            }



            uint8_t* payload = buf + hdr_size + csrc_size + ext_size;
            int payload_size = slen - hdr_size - csrc_size - ext_size;

            // todo: 删除，或者StreamCallback进行带宽估计
            // BWE入口
// only use subscriber estimation
//            if (rtc->video_xtrans && (ssrc == rtc->xtranses[RTC_VIDEO].remote_sources[0].src_id || ssrc == rtc->xtranses[RTC_VIDEO].remote_sources[1].src_id)) {
//                if (!rtc->pub_estimator) {
//                    rtc->pub_estimator = new bwe::InterArrivalEstimator(true, rtc->obj_id, rtc->xtranses[RTC_VIDEO].remote_sources[0].src_id);
//                }
//                uint32_t rtp_ts = be_get_u32(buf+4);
//                bool overuse = rtc->pub_estimator->IncomingPacket(ssrc, recv_ts, rtp_ts, slen);
//                if (overuse) {
//                    auto br = rtc->pub_estimator->TargetBitrate();
//                    rtc_endpoint_update_remb(rtc, ssrc, br);
//                }
//            }

            // 1. restore packet from RTX
            if (is_rtx) {
                uint16_t osn = be_get_u16(payload);
                if (rtc->is_dumping_rtp) {
                    dump_rtp_rtx(rtc, "restore from", buf, slen);
                }
                // restore ssrc
                if (ssrc == rtc->xtranses[RTC_VIDEO].remote_sources[1].src_id) {
                    ssrc = rtc->xtranses[RTC_VIDEO].remote_sources[0].src_id;
                } else if (ssrc == rtc->xtranses[RTC_AUDIO].remote_sources[1].src_id) {
                    ssrc = rtc->xtranses[RTC_AUDIO].remote_sources[0].src_id;
                }
                be_set_u32(ssrc, buf + 8);
                be_set_u16(osn, buf + 2);
                // restore pt and codec
                uint8_t m_and_pt = buf[1];
                m_and_pt &= 0x80u;
                m_and_pt |= codec->pt;
                buf[1] = m_and_pt;
                if (rtc->is_dumping_rtp) {
                    hex_print("moving 2 bytes forwards: ", payload + 2, 2);
                }
                memmove(payload, payload + 2, payload_size - 2);
                slen -= 2;
                payload_size -= 2;
            }
            // 2. restore packet from RED
            if (codec->id == rtc_codec_id_red) {
                uint8_t apt = *payload & 0x7Fu;
                if (rtc->is_dumping_rtp) {
                    dump_rtp_red(rtc, "restore from", buf, slen);
                }
                // restore pt
                uint8_t m_and_pt = buf[1];
                m_and_pt &= 0x80u;
                m_and_pt |= apt;
                buf[1] = m_and_pt;
                // restore codec
                codec = rtc_codecs_find_by_pt(rtc->codecs, apt);
                if (!codec) {
                    if (rtc->is_dumping_rtcp) {
                        rtc_dbgi("unable to restore red packet, inner codec pt=%d not recognized", apt);
                    }
                    break;
                }
                codec_id = codec->id;
                // skip 1 byte (block hdr) in payload
                memmove(payload, payload + 1, payload_size - 1);
                payload_size -= 1;
                slen -= 1;
            }
            // 3. extract protected codec from fec
            if (codec->id == rtc_codec_id_ulpfec) {
                uint8_t pt_recovery = payload[1] & 0x7Fu;
                if (pt_recovery) {
                    rtc_codec_t* fec_codec = rtc_codecs_find_by_pt(rtc->codecs, pt_recovery);
                    if (fec_codec) fec_codec_id = fec_codec->id;
                }
                if (rtc->is_dumping_rtp) {
                    dump_fec(payload, slen - payload_size);
                }
            }

            if (rtc->is_translating_fec && (codec->id == rtc_codec_id_vp8 || codec->id == rtc_codec_id_ulpfec)) {
                if (!rtc->ulpfec_decoder) {
                    rtc->ulpfec_decoder = new UlpfecDecoder(rtc->rtp_factory, rtc->codecs);
                }
                auto packet = rtc->rtp_factory->allocate();
                auto packet_size = packet->read(buf, slen, rtc->codecs);
                if (packet_size > 0 && packet->payload_size > 0) {
                    // todo: optimize the implementation of ulpfec decoder
                    packet->is_recovered = false;
                    rtc->ulpfec_decoder->add(packet);

                    std::list<std::shared_ptr<RtpPacket>> recovered_packets;
                    // todo: 统计恢复率
                    // auto packets_recoverd = rtc->ulpfec_decoder->recover(recovered_packets);

                    for (auto p : recovered_packets) {
                        if (rtc->is_dumping_rtp) {
                            dump_rtp(rtc, "recovered", p->buffer, p->buffer_size);
                        }
                        int ext_add = filter_extensions(rtc, p->buffer, p->buffer_size);
                        p->ext_len += ext_add;
                        p->buffer_size += ext_add;
                        p->payload += ext_add;
                        p->payload_size += ext_add;
                        if (rtc->is_dumping_rtp) {
                            dump_rtp(rtc, "recovered/filtered", p->buffer, p->buffer_size);
                        }

                        if(!rtc->tlv_with_raw_packet){
                            rtc_endpoint_write_tlv_rtp_rtcp(rtc->tlv1, is_rtcp, p->buffer, p->buffer_size);
                        }
                        
                        p->buffer[1] &= 0x80;
                        p->buffer[1] |= rtc_codec_default_pt(codec_id, fec_codec_id);
                        rtc_endpoint_call_listeners_stream_data(rtc, RTC_VIDEO,
                                p->codec_id, rtc_codec_id_unknown, RTC_RECV_CLEAR,
                                0, is_rtx, p->buffer, p->buffer_size);
                    }
                } else {
                    dbge("could not read fec packet");
                }
            }

            // 4. extract sps/pps from h264
            // we need to parse every h264 payload for such thing, because it could be updated
            if (codec->id == rtc_codec_id_h264) {
                h264::nal_packet nalp;
                int nalp_read = nalp.read(buf + hdr_size, slen - hdr_size);
                if (nalp_read && rtc->is_dumping_rtp) {
                    dbgi("recv h264: %s", nalp.to_string().c_str());
                }
                if (nalp_read && nalp.header.type == h264::STAP_A) {
                    for (auto& unit : nalp.stap_a.units) {
                        if (unit.header.type == h264::SPS || unit.header.type == h264::PPS) {
                            memcpy(rtc->sps_pps_buf, buf, slen);

                            rtc->sps_pps_buf[1] |= 0x80;
                            rtc->sps_pps_len = slen;
                            break;
                        }
                    }
                }
            }

            int ext_add = filter_extensions(rtc, buf, slen);
            slen += ext_add;
            hdr_size += ext_add;
            if (rtc->is_dumping_rtp) {
                dump_rtp(rtc, "recv", buf, slen);
            }
        }else{
            slen = xdtls_srtp_unprotect_rtcp(xtrans->dtls, (unsigned char *)buf, buf_len);
            if (rtc->is_dumping_rtcp) {
                dump_rtcp(rtc, "recv <------ ", buf, slen);
            }
            if(slen <= 0) break;
            
            if(rtc->tlv_with_raw_packet){
                rtc_endpoint_write_tlv_rtp_rtcp(rtc->tlv1, is_rtcp, buf, slen);
            }
            
            ssrc = rtcp_get_sender_ssrc(buf, slen);
            uint8_t pt = buf[1];
            switch (pt) {
                case RTCP_SR: { /* sender report */
    //                uint64_t ntp_msb = be_get_u32(buf+8);
    //                uint64_t ntp_lsb = be_get_u32(buf+12);
    //                uint64_t ntp_ts = ntp_msb << 32 | ntp_lsb;
                    uint32_t rtp_ts = be_get_u32(buf + 16);
                    if (ssrc == rtc->xtranses[RTC_VIDEO].remote_sources[0].src_id) {
                        rtc->xtranses[RTC_VIDEO].remote_sources[0].last_rtcp_ts = rtp_ts;
                        rtc->xtranses[RTC_VIDEO].remote_sources[0].last_rtcp_ms = get_timestamp_ms();
                    }
                    break;
                }
            }

            //rtc_dbgi("recv rtcp. sender ssrc(%u) pt = %u", ssrc, pt);
//            if(pt == RTCP_RR){
//                unsigned char rc = (buf[0]>>0) & 0x1F;
//                uint32_t sender_ssrc = be_get_u32(buf + 4);
//                for (unsigned char uc = 0; uc < rc; uc++) {
//                    uint32_t receiver_ssrc = be_get_u32(buf + 8 + uc * 24);
//                    rtc_dbgi("(%u)recv RR. send:%u >> recv:%u", ssrc, sender_ssrc, receiver_ssrc);
//                }
//            }

            int ssrc_index;

            rtc_nice_transport* rtcp_xtrans = rtc_endpoint_get_transport_by_remote_ssrc(rtc, ssrc, &ssrc_index);
            if (!rtcp_xtrans) {
                //TODO 为什么通过ssrc找不到，还要取一下 video transport，还要set 下
                rtcp_xtrans = rtc_endpoint_get_video_transport_and_set_ssrc(rtc, ssrc, &ssrc_index);
            }
            if (rtcp_xtrans) {
                if (rtcp_xtrans == &rtc->xtranses[RTC_AUDIO]) {
                    replace_rtcp_ssrc_to_placeholder(buf, slen, placeholder::audio, rtcp_xtrans);
                } else if (rtcp_xtrans == &rtc->xtranses[RTC_VIDEO]) {
                    replace_rtcp_ssrc_to_placeholder(buf, slen, placeholder::video, rtcp_xtrans);
                }
            }

        }

        if(!rtc->tlv_with_raw_packet){
            rtc_endpoint_write_tlv_rtp_rtcp(rtc->tlv1, is_rtcp, buf, slen);
        }
        
        if (!is_rtcp) {
            buf[1] &= 0x80;
            buf[1] |= rtc_codec_default_pt(codec_id, fec_codec_id);
        }


        int ssrc_index;
        rtc_nice_transport * local_xtrans = rtc_endpoint_get_transport_by_remote_ssrc(rtc, ssrc, &ssrc_index);
        if(!local_xtrans && is_rtcp){
            //TODO 为什么通过ssrc找不到，还要取一下 video transport，还要set 下
            local_xtrans = rtc_endpoint_get_video_transport_and_set_ssrc(rtc, ssrc, &ssrc_index);
            if(local_xtrans){
                rtc_dbgi("set remote ssrc[%d]=[%u]", ssrc_index, ssrc);
                dump_rtcp(rtc, "set-ssrc", buf, slen);
            }
        }
        
        if(!local_xtrans){
            rtc_dbgi("unknown ssrc %u, is_rtcp=%d, rtc[%s]", ssrc, is_rtcp, rtc_endpoint_get_id(rtc));
            if(is_rtcp){
                dump_rtcp(rtc, "unknown-ssrc", buf, slen);
            }else{
                dump_rtp(rtc, "unknown-ssrc", buf, slen);
            }
            break;
        }

        if(is_rtcp){
            buf = rtc_endpoint_filter_recv_rtcp(rtc, local_xtrans, buf, &slen );
            if(!buf){
                rtc_dbge("filter rtcp fail");
                break;
            }
//            rtc_endpoint_call_listeners_stream_data(rtc, local_xtrans->stream_type, codec_id, fec_codec_id, RTC_RECV_RAW, is_rtcp, is_rtx, buf, slen);
//            rtc_dbgv("%s: recv_rtp_packets=%llu, recv_rtp_payload_bytes=%llu, sent_rtp_packets=%llu, sent_rtp_payload_bytes=%llu"
//                     , local_xtrans->mid, local_xtrans->recv_rtp_packets, local_xtrans->recv_rtp_payload_bytes
//                     , local_xtrans->sent_rtp_packets, local_xtrans->sent_rtp_payload_bytes);
            
        }else{
//            rtc_endpoint_call_listeners_stream_data(rtc, local_xtrans->stream_type, codec_id, fec_codec_id, RTC_RECV_RAW, is_rtcp, is_rtx, buf, slen);
            int payload_bytes = parse_rtp_bytes(buf, slen);
            if(payload_bytes < 0) break;
            ++local_xtrans->recv_rtp_packets;
            local_xtrans->recv_rtp_payload_bytes += payload_bytes;
        }
        
        if(slen <= 0) {
            rtc_dbge("something wrong, slen==0");
            break;
        }

        // TODO: ==> check ssrc_index ?   (ssrc_index == 0)
        local_xtrans->remote_sources[ssrc_index].packet_count++;
        if(!is_rtcp && local_xtrans->nack_req_cache){
            uint32_t seq = be_get_u16(buf+2);
            xmcache_put_rtp(local_xtrans->nack_req_cache, seq, !IS_KEY_FRAME, codec_id, NULL, 0);
        }

        rtc_endpoint_call_listeners_stream_data(rtc, local_xtrans->stream_type, codec_id, fec_codec_id, RTC_RECV_CLEAR, is_rtcp, is_rtx, buf, slen);
        // nacks handler is not rate throttled, when benchmarking, we disable it
        if (rtc->enable_nack_handler) {
            if (rtc_codec_type(codec_id) == rtc_codec_type_video || codec_id == rtc_codec_id_ulpfec) {
                std::list<nack_request> requests;
                if (!local_xtrans->nacks) {
                    local_xtrans->nacks = nack_handler_new();
                }
                nack_handler_receive_seq(local_xtrans->nacks, recv_ts, be_get_u16(buf + 2), requests);
                if (!rtc->connector || rtc_connector_sub_count(rtc->connector) == 0) { //rtc sub，或 没有被订阅的pub rtc
                    for (auto& r : requests) {
                        uint8_t * buf = rtc->send_work_buf;
                        int len = 0;
                        len += make_rtcp_rr_info(buf+len, local_xtrans->local_sources[0].src_id, 0);
                        len += make_rtcp_nack(buf+len, local_xtrans->local_sources[0].src_id, local_xtrans->remote_sources[0].src_id, r.pid, r.blp);
                        send_rtcp_data(rtc, local_xtrans, xtrans, buf, len);
                    }
                }
            }
        }
    }while(0);
}

#include <random>
#include <nice/address.h>
//static
//int simulate_dtls_drop(rtc_nice_transport *xtrans){
//    rtc_endpoint_t rtc = xtrans->owner;
//    std::random_device rd;
//    std::mt19937 mt(rd());
//    int n = mt();
//
//    if((n % 3) == 0){
//        rtc_dbgi("pass random n = %d", n);
//        return 0;
//    }else{
//        rtc_dbgi("drop random n = %d", n);
//        return 1;
//    }
//
//}


static
void cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id,
                  guint len, gchar *buf_, gpointer data){

    rtc_endpoint_t rtc = (rtc_endpoint_t) data;
    rtc_nice_transport * xtrans = &rtc->xtranses[stream_id-1];
    
    unsigned char * buf =(unsigned char * )buf_;
    unsigned char first_byte = (unsigned char)buf[0];

//        rtc_dbgi("ice [stream %d, component %d] got bytes : %d first_byte=%d", stream_id, component_id, len,first_byte);
    
    if(first_byte > 127 && first_byte < 192){ // rtp/rtcp
        rtc_endpoint_heart_beat(rtc, "rtp-rtcp");
        process_recv_rtp_rtcp(rtc, xtrans, buf, len);
        
    }else if(first_byte > 19 && first_byte < 64){ // dtls
        rtc_dbgv("got dtls packet (stream＝%d:%d, %d bytes), first_byte=%d", stream_id, component_id, len, first_byte);
        rtc_endpoint_heart_beat(rtc, "dtls");
        xtrans->recv_dtls_bytes += len;
//        if(simulate_dtls_drop(xtrans)){
//            rtc_dbgi("drop dtls for testing, recv No.%lld",  xtrans->dtls_recv_packets);
//            return ;
//        }
        
        xtrans->dtls_recv_packets++;
        
        int setup = 0;
        xdtls_srtp_input_data(xtrans->dtls, (const unsigned char *)buf, len, &setup);
        rtc_dbgi("xtran mid %s-%p, dtls setup: %d, srtp ready: %d", xtrans->mid, xtrans, setup, xdtls_srtp_is_ready(xtrans->dtls));

        if(setup){
            setup = 0;
            rtc_nice_transport * x = rtc->audio_xtrans;
            if(x){
                if (x->dtls && xdtls_srtp_is_ready(x->dtls)) {
                    x = rtc->video_xtrans;
                    if(x) {
                        if (x->dtls && xdtls_srtp_is_ready(x->dtls)) {
                            setup = 1;
                        }
                    } else {
                        setup = 1;
                    }
                }
            } else {
                x = rtc->video_xtrans;
                if(x) {
                    if (x->dtls && xdtls_srtp_is_ready(x->dtls)) {
                        setup = 1;
                    }
                }
            }

            if(rtc->audio_xtrans){
                rtc_dbgi("audio_xtrans %s-%p, srtp ready: %d, setup = %d",
                        rtc->audio_xtrans->mid, rtc->audio_xtrans, xdtls_srtp_is_ready(rtc->audio_xtrans->dtls), setup);
            }else{
                rtc_dbgi("audio_xtrans %p", rtc->audio_xtrans);
            }
            if(rtc->video_xtrans){
                rtc_dbgi("video_xtrans %s-%p, srtp ready: %d, setup = %d",
                         rtc->video_xtrans->mid, rtc->video_xtrans, xdtls_srtp_is_ready(rtc->video_xtrans->dtls), setup);
            }else{
                rtc_dbgi("video_xtrans %p", rtc->video_xtrans);
            }

            if(setup){
                do {
		    rtc_dbgi("nice selected (steam_id / component_id) = (%d / %d)",  stream_id, component_id);
                    // print srtp decode parameters
                    NiceCandidate* lc = nullptr;
                    NiceCandidate* rc = nullptr;
                    auto ok = nice_agent_get_selected_pair(agent, stream_id, component_id, &lc, &rc);
                    if (!ok || lc == nullptr || rc == nullptr) {
                        break;
                    }
		    rtc_dbgi("successfully got selected pair!");

                    char lca[64] = {0,};
                    char rca[64] = {0,};

                    nice_address_to_string(&lc->addr, lca);
                    nice_address_to_string(&rc->addr, rca);

                    unsigned short lcp = nice_address_get_port(&lc->addr);
                    unsigned short rcp = nice_address_get_port(&rc->addr);

                    unsigned char* lk = nullptr;
                    unsigned char* rk = nullptr;
                    int lkl = 0;
                    int rkl = 0;
                    if (xdtls_srtp_get_keys(xtrans->dtls, &lk, &lkl, &rk, &rkl)) {
                        break;
                    }
                    if (lkl > 64 || rkl > 64) {
                        break;
                    }

                    char lks[128] = {0,};
                    char rks[128] = {0,};

                    for (int i = 0; i < lkl; i++) {
                        sprintf(lks + i * 2, "%02x", lk[i]);
                    }

                    for (int i = 0; i < rkl; i++) {
                        sprintf(rks + i * 2, "%02x", rk[i]);
                    }

                    char str[1024] = {0,};
                    snprintf(str, sizeof(str), "-d \"%s:%u=}%s:%u,%.*s\" -d \"%s:%u=}%s:%u,%.*s\"",
                             lca, lcp, rca, rcp, lkl * 2, lks,
                             rca, rcp, lca, lcp, rkl * 2, rks);
                    rtc_dbgi("srtp decoder params: %s", str);
                } while(0);

                rtc->is_setup = 1;
                rtc_endpoint_open_tlv(rtc);
                rtc_endpoint_req_pli(rtc);
                rtc_dbgi("all xtrans setup, fire setup event");
                rtc_endpoint_call_listeners_event(rtc, RTC_EVENT_SETUP, NULL, 0);
            }
        }
    }else if(first_byte < 2){
        rtc_dbgv("got stun packet first_byte=%d", first_byte);
        rtc_endpoint_heart_beat(rtc, "stun");
        xtrans->recv_ice_bytes += len;
    }else{
        rtc_dbgv("got unknown packet first_byte=%d", first_byte);
        xtrans->recv_unknown_bytes += len;
    }
    
}


static int on_sending_dtls_data(xdtls_srtp_t dtls,  void * cb_context, const unsigned char * data, int length){
    rtc_nice_transport * xtrans = (rtc_nice_transport *) cb_context;
    rtc_endpoint_t rtc = xtrans->owner;
    
//    if(simulate_dtls_drop(xtrans)){
//        rtc_dbgi("drop dtls for testing, sent No.%lld",  xtrans->dtls_sent_packets);
//        return length;
//    }
    
    xtrans->dtls_sent_packets++;
    

    
    guint stream_id = xtrans->stream_id;
    guint component_id = 1;
    gint sent_bytes = nice_agent_send (
                                       rtc->agent,
                                       stream_id,
                                       component_id,
                                       length,
                                       (const gchar *)data);
    if (sent_bytes > 0) {
        xtrans->send_dtls_bytes += sent_bytes;
    }
    rtc_dbgv("on_sending_dtls_data: stream_id=%d, %d/%d bytes", stream_id, sent_bytes, length);
    return sent_bytes;
}

static inline
void destroy_component_candidate_strings(rtc_nice_component* comp) {
    if(comp->candidate_strings){
        for (int j = 0; j < comp->candidate_count; ++j) {
            g_free(comp->candidate_strings[j]);
            comp->candidate_strings[j] = NULL;
        }
        g_free(comp->candidate_strings);
        comp->candidate_strings = NULL;
    }
}

static inline
void destroy_dtls_timer(rtc_nice_transport * xtrans){
    if(xtrans->dtls_timer != NULL) {
        g_source_destroy(xtrans->dtls_timer);
        g_source_unref(xtrans->dtls_timer);
        xtrans->dtls_timer = NULL;
    }
}

static
void rtc_endpoint_delete_transport(rtc_endpoint_t rtc, rtc_nice_transport * xtrans){
    
    destroy_dtls_timer(xtrans);
    
    if(xtrans->dtls){
        xdtls_srtp_destroy(xtrans->dtls);
        xtrans->dtls = NULL;
    }
    
    if(xtrans->sent_cache){
        xmcache_delete(xtrans->sent_cache);
        xtrans->sent_cache = NULL;
    }
    
    if(xtrans->nack_req_cache){
        xmcache_delete(xtrans->nack_req_cache);
        xtrans->nack_req_cache = NULL;
    }
    
    for(int i = 0; i < 2; i++){
//        if(xtrans->comps[i].candidate_strings){
//            g_free(xtrans->comps[i].candidate_strings);
//            xtrans->comps[i].candidate_strings = NULL;
//        }
        
        destroy_component_candidate_strings(&xtrans->comps[i]);
    }
    
    if (xtrans->local_ufrag){
        g_free(xtrans->local_ufrag);
        xtrans->local_ufrag = NULL;
    }
    
    if (xtrans->local_password){
        g_free(xtrans->local_password);
        xtrans->local_password = NULL;
    }

    if (xtrans->nacks) {
        nack_handler_delete(xtrans->nacks);
        xtrans->nacks = nullptr;
    }
    xtrans->enabled = 0;
}

static inline
void rtc_endpoint_print_statistics(rtc_endpoint_t rtc){
    int strlen = 0;
    char * strbuf = (char *)rtc->recv_work_buf;
    int strbufsz = sizeof(rtc->recv_work_buf);
    strlen += snprintf(strbuf+strlen, strbufsz-strlen, "rtc_statistics={");
    for(int i = 0; i < 2; i++){
        rtc_nice_transport * xtrans = &rtc->xtranses[i];
        
        
        if(i == 0){
            strlen += snprintf(strbuf+strlen, strbufsz-strlen, "\"stream_%s\":{", xtrans->mid);
        }else{
            strlen += snprintf(strbuf+strlen, strbufsz-strlen, ",\"stream_%s\":{", xtrans->mid);
        }
        
        strlen += snprintf(strbuf+strlen, strbufsz-strlen, ",\"recv_rtp_packets\":%lu", xtrans->recv_rtp_packets);

        strlen += snprintf(strbuf+strlen, strbufsz-strlen, ",\"sent_rtp_packets\":%lu", xtrans->sent_rtp_packets);
        
        strlen += snprintf(strbuf+strlen, strbufsz-strlen, ",\"recv_dtls_pkt\":%ld", xtrans->dtls_recv_packets);
        strlen += snprintf(strbuf+strlen, strbufsz-strlen, ",\"sent_dtls_pkt\":%ld", xtrans->dtls_sent_packets);
        
        
        //        rtc_dbgi("stream[%d], statistics[recv_dtls_pkt]=%lld", xtrans->stream_id, xtrans->dtls_recv_packets);
        //        rtc_dbgi("stream[%d], statistics[sent_dtls_pkt]=%lld", xtrans->stream_id, xtrans->dtls_sent_packets);
        
       //  NiceStatistics statistics[16];
       //  for(int comp_id = 1; comp_id <=2; comp_id++){
       //      int num = 0;
       //      gboolean ok = nice_agent_get_component_statistics (rtc->agent,
       //                                                         xtrans->stream_id,
       //                                                         comp_id, statistics, 16, &num);
       //      if(ok){
       //          strlen += snprintf(strbuf+strlen, strbufsz-strlen, ",\"comp%d\":{", comp_id);
       //          strlen += snprintf(strbuf+strlen, strbufsz-strlen, "\"%s\":%llu", statistics[0].name, (uint64_t)statistics[0].value_uint64);
       //          for(int n = 1; n < num; n++){
       //              //                    rtc_dbgi("stream[%d], comp[%d], statistics[%s]=%lu", xtrans->stream_id, comp_id
       //              //                         , statistics[n].name, statistics[n].value_uint64);
       //              strlen += snprintf(strbuf+strlen, strbufsz-strlen, ",\"%s\":%llu", statistics[n].name, (uint64_t)statistics[n].value_uint64);
       //          }
       //          strlen += snprintf(strbuf+strlen, strbufsz-strlen, "}");
       //      }else{
       //          rtc_dbge("stream[%d], comp[%d], no statistics", xtrans->stream_id, comp_id);
       //      }
       //      
       //  }
        strlen += snprintf(strbuf+strlen, strbufsz-strlen, "}");
        
    }
    strlen += snprintf(strbuf+strlen, strbufsz-strlen, "}");
    rtc_dbgi("%s", strbuf);
}

static
void rtc_endpoint_uninit_transports(rtc_endpoint_t rtc){
    if(!rtc) return;
    
    rtc_endpoint_print_statistics(rtc);
    
    for(int i = 0; i < 2; i++){
        rtc_nice_transport * xtrans = &rtc->xtranses[i];
        rtc_endpoint_delete_transport(rtc, xtrans);
        if (rtc->agent) {
            for(int j = 0; j < 2; ++j) {
                nice_agent_attach_recv(rtc->agent, xtrans->stream_id, j+1,
                                        rtc->main_context, NULL, NULL);
            }
            // rtc_dbge("nice_agent_remove_stream stream_id = %d", xtrans->stream_id);
            nice_agent_remove_stream(rtc->agent, xtrans->stream_id);
        }
    }
    
    if(rtc->agent){
        g_object_unref(rtc->agent);
        rtc->agent = NULL;
    }

}

static
gchar ** rtc_endpoint_pull_comp_candidates(rtc_endpoint_t rtc, int stream_id, int comp_id, int * pcandidate_count, std::uint32_t &firstUdpPort){
    GSList * cands = NULL;
    gchar ** cand_strings = NULL;
    int candidate_count = 0;
    int ret = -1;
    do{
        cands = nice_agent_get_local_candidates(rtc->agent, stream_id, comp_id);
        if (cands == NULL){
            rtc_dbge("nice_agent_get_local_candidates fail, stream_id=%d, comp=%d", stream_id, comp_id);
            ret = -1;
            break;
        }
        candidate_count = g_slist_length(cands);
        
        cand_strings = (gchar **)g_malloc(sizeof(gchar*) * candidate_count);
        GSList * item;
        int n = 0;
        for (item = cands; item; item = item->next, n++) {
            NiceCandidate *c = (NiceCandidate *)item->data;

            if(firstUdpPort == 0 || firstUdpPort > 65535){
                firstUdpPort = ntohs(c->addr.s.ip4.sin_port);
            }
            //  candidate sdp,     see rfc5245#section-15
            gchar* local_candidate = nice_agent_generate_local_candidate_sdp (rtc->agent, c);
            if(app_config_get()->media_ip2){
                std::string tmp(local_candidate);
                std::string mediaIp(app_config_get()->media_ip);
                std::string mediaIp2(app_config_get()->media_ip2);
                tmp.replace(tmp.find(mediaIp), mediaIp.size(), mediaIp2);

                cand_strings[n] = g_strdup(tmp.c_str());
                dbgi("candidate ip chanage: %s -> %s, final: %s", mediaIp.c_str(), mediaIp2.c_str(), tmp.c_str());
            }else{
                cand_strings[n] = local_candidate;
            }
        }
        *pcandidate_count = n;
        ret = 0;
    }while(0);
    
    if(cands){
        g_slist_free_full(cands, (GDestroyNotify)&nice_candidate_free);
        cands = NULL;
    }
    
    if(ret){
        if(cand_strings){
            g_free(cand_strings);
            cand_strings = NULL;
        }
    }

    return cand_strings;
    
}

static
gboolean dtls_timeout_handler(gpointer user_data){
    rtc_nice_transport * xtrans = (rtc_nice_transport *) user_data;
    rtc_endpoint_t rtc = xtrans->owner;
    if(xdtls_srtp_is_ready(xtrans->dtls)){
        rtc_dbgv("dtls is ready, remove timer, stream_id=%d", xtrans->stream_id);
        destroy_dtls_timer(xtrans);
        return FALSE;
    }
    
    if(xtrans->dtls_kick_time > 0){
        int64_t elapsed = get_timestamp_ms() - xtrans->dtls_kick_time;
//        if(elapsed > 5000){
//            if(xtrans->dtls_kick_num >= 10){
//                rtc_dbgi("reach max kick dtls, stream_id=%d, kicknum=%d", xtrans->stream_id, xtrans->dtls_kick_num);
//                destroy_dtls_timer(xtrans);
//                return FALSE;
//            }
//            xtrans->dtls_kick_num++;
//            xtrans->dtls_kick_time = get_timestamp_ms();
//            rtc_dbgi("kick dtls handshake, stream_id=%d, kicknum=%d", xtrans->stream_id, xtrans->dtls_kick_num);
//            xdtls_srtp_start_handshake(xtrans->dtls, 1);
//            return TRUE;
//        }
        
//        if(elapsed > 60000){
//            rtc_dbgi("reach max dtls timeout 60s, stream_id=%d, kicknum=%d", xtrans->stream_id, xtrans->dtls_kick_num);
//            destroy_dtls_timer(xtrans);
//            return TRUE;
//        }
        
        int64_t timeout_value = 0;
        int ret = xdtls_srtp_check_timeout(xtrans->dtls, &timeout_value);
        if(ret && elapsed < 6000){
            rtc_dbgi("dtls timeout, stream_id=%d, next-timeout=%ld", xtrans->stream_id, timeout_value);
        }
    }
    
    return TRUE;
}

static inline
int send_rtp_data(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans, rtc_nice_transport * xtrans, rtc_codec_id_t codec_id, rtc_codec_id_t fec_codec_id, unsigned char * buf, int len, bool is_rtx){
    // use a seperate buffer, so that we can prepend a byte to payload (needed by RED and RTX)
    // [ hdr ] [ inserted bytes ] [payload]
    unsigned char snd_buf[3000];
    int inserted = 0;
    int is_red = 0;
    int ret = -1;
    int64_t now = get_timestamp_ms();
    do{
        if(rtc->remote_sdp_state != SDP_STATE_ANSWER && rtc->remote_sdp_state != SDP_STATE_OFFER){
            ret = -1;
            break;
        }

        rtc_codec_t* codec = rtc_codecs_find_by_id(rtc->codecs, codec_id);
        if (!codec) {
            //if (rtc->is_dumping_rtp) {
                rtc_dbgv("unable to send not negotiated codec=%s ", rtc_codec_name(codec_id));
            //}
            ret = -1;
            break;
        }

        if(local_xtrans->sent_cache && !is_rtx && (codec_id == rtc_codec_id_h264 || codec_id == rtc_codec_id_vp8)){
            uint16_t seq = be_get_u16(buf + 2);
            seq = translate_seq(local_xtrans, seq);
            xmcache_put_rtp(local_xtrans->sent_cache, seq, !IS_KEY_FRAME, codec_id, buf, len);
        }

        // we calculate |hdr_size| when we receive and dump a rtp packet,
        // and we have to calculate here again,
        // maybe using a RTPPacket class to hold those calculated things is easier
        uint8_t p = buf[0] >> 5 & 0x1u;
        uint8_t x = buf[0] >> 4 & 0x1u;
        uint8_t cc = buf[0] & 0x0fu;
        // uint32_t ssrc = be_get_u32(buf + 8);

        uint16_t hdr_size = 12 + 4 * cc;
        uint16_t pad_size = 0;
        // if extension is present
        if (x && buf[hdr_size] == 0xbe && buf[hdr_size+1] == 0xde) {
            uint8_t* ext = buf + hdr_size;
            uint16_t ext_size = 4 + 4 * be_get_u16(buf + hdr_size + 2);
            uint8_t* payload = ext + ext_size;
            uint16_t payload_size = len - ext_size - hdr_size;

//            uint8_t *ext_write = buf + hdr_size + 4;
//            uint8_t *ext_end = buf + hdr_size + 4 + 4 * ext_size;

            bool has_transport_seq = false;
            uint16_t transport_seq = 0;
            bool has_abs_send_time = false;
            uint32_t abs_send_time = 0;

            int parsed_ext_size = 0;
            // 老代码都是先从xtrans判断是哪个流，其实用ssrc更好
            if (local_xtrans == &rtc->xtranses[RTC_VIDEO]) {
                parsed_ext_size = parse_rtp_header_extension(buf + hdr_size, ext_size, [&](uint8_t et, unsigned char* ext, int size) {
                    auto id = rtc->rtp_extens_video->getId(RtpExtensionType(et));

                    switch (et) {
                    case RtpExtensionType::AbsSendTime:
                        ext[0] = id << 4 | 2;
                        has_abs_send_time = true;
                        abs_send_time = be_get_u24(ext+1);
                        return true;
                    case RtpExtensionType::TransportSequenceNumber:
                        ext[0] = id << 4 | 1;
                        has_transport_seq = true;
                        transport_seq = be_get_u16(ext+1);
                        return true;
                    }

                    return false;
                });

                if (rtc->enable_bwe) {
                    // transport-wide-seq
                    if (!has_transport_seq && rtc->remote_sdpinfo->video_stream->transport_cc_enabled) {
                        uint8_t id = rtc->rtp_extens_video->getId(RtpExtensionType::TransportSequenceNumber);
                        buf[hdr_size + parsed_ext_size + 4] = id << 4 | 1;
                        be_set_u16(local_xtrans->transport_seq_num, buf + hdr_size + parsed_ext_size + 1);
                        parsed_ext_size += 3;
                        transport_seq = local_xtrans->transport_seq_num;
                        has_transport_seq = true;
                        ++local_xtrans->transport_seq_num;
                    }
                    // abs-send-time
                    if (!has_abs_send_time && rtc->remote_sdpinfo->video_stream->abs_send_time_enabled) {
                        uint8_t id = rtc->rtp_extens_video->getId(RtpExtensionType::AbsSendTime);
                        buf[hdr_size + parsed_ext_size + 4] = id << 4 | 2;
                        uint32_t sec = (now / 1000) & 0x3F;
                        uint32_t frac = (now % 1000) * (1000.0 / (1 << 18));
                        uint32_t abs_send_time = ((sec << 18) | frac) & 0xFFFFFF;
                        be_set_u24(abs_send_time, buf + hdr_size + parsed_ext_size + 1);
                        parsed_ext_size += 4;
                    }
                }
            } else if (local_xtrans == &rtc->xtranses[RTC_AUDIO]) {
                parsed_ext_size = parse_rtp_header_extension(buf + hdr_size, ext_size, [&](uint8_t et, unsigned char* ext, int size) {
                    auto id = rtc->rtp_extens_audio->getId(RtpExtensionType(et));

                    switch (et) {
                    case RtpExtensionType::AbsSendTime:
                        ext[0] = id << 4 | 2;
                        has_abs_send_time = true;
                        abs_send_time = be_get_u24(ext+1);
                        return true;
                    case RtpExtensionType::SSRCAudioLevel:
                        ext[0] = id << 4 | 0;
                        return true;
                    }

                    return false;
                });
            }

            if (parsed_ext_size == 4) {
                parsed_ext_size = 0;
                // clear x bit
                buf[0] &= 0xef;
                memmove(ext, payload, payload_size);
            } else {
                be_set_u16((parsed_ext_size - 4) / 4, ext + 2);
                memmove(ext + parsed_ext_size, payload, payload_size);
            }

            len = hdr_size + parsed_ext_size + payload_size;
            hdr_size += parsed_ext_size;

//            if (buf[hdr_size] == 0xBE && buf[hdr_size+1] == 0xDE) {
//                if (local_xtrans == &rtc->xtranses[RTC_VIDEO] && rtc->rtp_extens_video) {
//                    // translate video rtp extension header using rtc->rtp_extens_video
//                    while (ext_read < ext_end) {
//                        if (ext_read[0] != 0) {
//                            RtpExtensionType type = RtpExtensionType(ext_read[0] >> 4);
//                            uint8_t l = ext_read[0] & 0x0F;
//                            uint8_t id = rtc->rtp_extens_video->getId(type);
//                            if (id != 0) {
//                                if (type == RtpExtensionType::AbsSendTime && l == 2) {
//                                    ext_write[0] = id << 4 | 2;
//                                    ext_write[1] = ext_read[1];
//                                    ext_write[2] = ext_read[2];
//                                    ext_write[3] = ext_read[3];
//
//                                    has_abs_send_time = true;
//                                    abs_send_time = be_get_u24(ext_read + 1);
//                                    abs_send_time = abs_send_time * 1000 >> 18;
//
//                                    ext_read += 4;
//                                    ext_write += 4;
//                                } else if (type == RtpExtensionType::TransportSequenceNumber && l == 1) {
//                                    ext_write[0] = id << 4 | 1;
//                                    ext_write[1] = ext_read[1];
//                                    ext_write[2] = ext_read[2];
//
//                                    has_transport_seq = true;
//                                    transport_seq = be_get_u16(ext_read + 1);
//
//                                    ext_read += 3;
//                                    ext_write += 3;
//                                } else {
//                                    ext_read += 1 + l + 1;
//                                }
//                            } else {
//                                ext_read += 1 + l + 1;
//                            }
//                        } else {
//                            ext_read += 1;
//                        }
//                    }
//                    // add missing rtp extension header to do bwe
//                    if (local_xtrans == &rtc->xtranses[RTC_VIDEO] && rtc->enable_bwe) {
//                        // transport-wide-seq
//                        if (!has_transport_seq && rtc->remote_sdpinfo->video_stream->transport_cc_enabled) {
//                            uint8_t id = rtc->rtp_extens_video->getId(RtpExtensionType::TransportSequenceNumber);
//                            ext_write[0] = id << 4 | 1;
//                            be_set_u16(local_xtrans->transport_seq_num, ext_write + 1);
//                            ext_write += 3;
//                            transport_seq = local_xtrans->transport_seq_num;
//                            has_transport_seq = true;
//                            ++local_xtrans->transport_seq_num;
//                        }
//                        // abs-send-time
//                        if (!has_abs_send_time && rtc->remote_sdpinfo->video_stream->abs_send_time_enabled) {
//                            uint8_t id = rtc->rtp_extens_video->getId(RtpExtensionType::AbsSendTime);
//                            ext_write[0] = id << 4 | 2;
//                            uint32_t sec = (now / 1000) & 0x3F;
//                            uint32_t frac = (now % 1000) * (1000.0 / (1 << 18));
//                            uint32_t abs_send_time = ((sec << 18) | frac) & 0xFFFFFF;
//                            be_set_u24(abs_send_time, ext_write + 1);
//                            ext_write += 4;
//                        }
//                    }
//                } else if (local_xtrans == &rtc->xtranses[RTC_AUDIO] && rtc->rtp_extens_audio) {
//                    while (ext_read < ext_end) {
//                        if (ext_read[0] != 0) {
//                            RtpExtensionType type = RtpExtensionType(ext_read[0] >> 4);
//                            uint8_t l = ext_read[0] & 0x0F;
//                            uint8_t id = rtc->rtp_extens_audio->getId(type);
//                            if (id != 0) {
//                                if (type == RtpExtensionType::SSRCAudioLevel && l == 0) {
//                                    ext_write[0] = id << 4 | l;
//                                    ext_write[1] = ext_read[1];
//                                    ext_read += 2;
//                                    ext_write += 2;
//                                } else if (type == RtpExtensionType::CSRCAudioLevel) {
//                                    ext_write[0] = id << 4 | l;
//                                    for (int i = 0; i < l+1; ++i) {
//                                        ext_write[i+1] = ext_read[i+1];
//                                    }
//                                    ext_read += l+2;
//                                    ext_write += l+2;
//                                } else {
//                                    ext_read += l+2;
//                                }
//                            } else {
//                                ext_read += l+2;
//                            }
//                        } else {
//                            ext_read += 1;
//                        }
//                    }
//                }
//            } // finished extension handling
            // feed bwe
            if (rtc->enable_bwe && has_transport_seq && !is_rtx) {
                if (rtc->sub_estimator == nullptr) {
                    // todo: enable such debug from options
                    rtc->sub_estimator = new bwe::InterArrivalEstimator(false, rtc->obj_id,
                                                                        rtc->xtranses[RTC_VIDEO].local_sources[0].src_id);
                }
                rtc->sub_estimator->OutgoingPacket(transport_seq, be_get_u32(buf + 4), len);
            }

//            size_t written = ext_write - buf - hdr_size - 4;
//            // if no extension written, clear x totally erase extensions
//            // if one or more written, add padding
//            if (written == 0) {
//                buf[0] &= 0xEF;
//                ext_write -= 4; // 0xBE 0xDE and len
//            } else {
//                while (written % 4) {
//                    *ext_write++ = 0;
//                    written++;
//                }
//                be_set_u16(written / 4, buf + hdr_size + 2);
//            }
//
//            size_t payload_remains = len - (ext_read - buf);
//            memmove(ext_write, ext_end, payload_remains);
//            hdr_size = ext_write - buf;
//            len -= ext_read - ext_write;
        } // if (x)
        // todo: we should add missing rtp extension here

        if (p) {
            pad_size = buf[len - 1];
        }

        int payload_bytes = len - hdr_size - pad_size;
        // copy header to snd_buf
        memcpy(snd_buf, buf, hdr_size);

        // translate pt, will be overwritten if muxed into RED or RTX
        snd_buf[1] &= 0x80u;
        snd_buf[1] |= codec->pt;
        // translate seq, will be overwritten if muxed into RTX
        uint16_t seq = be_get_u16(snd_buf + 2);
        seq = translate_seq(local_xtrans, seq);
        be_set_u16(seq, snd_buf + 2);
        // translate ssrc, will be overwritten if muxed into RTX
        be_set_u32(local_xtrans->local_sources[0].src_id, snd_buf + 8);

        bool is_red_muxable = false;
        switch (codec_id) {
            case rtc_codec_id_vp8:
            case rtc_codec_id_h264:
            case rtc_codec_id_ulpfec:
                is_red_muxable = true;
                break;
            default:
            break;
        }
        rtc_codec_t* red_codec = rtc_codecs_find_by_id(rtc->codecs, rtc_codec_id_red);
        if (is_red_muxable && red_codec) {
            if (rtc->is_dumping_rtp) {
                dump_rtp(rtc, "red muxing", buf, len);
            }
            // modify pt
            snd_buf[1] &= 0x80u;
            snd_buf[1] |= red_codec->pt;
            if (inserted) {
                memmove(snd_buf + hdr_size + 1, snd_buf + hdr_size, inserted);
            }
            // insert block hdr (|F=0|pt|)
            snd_buf[hdr_size + inserted] = codec->pt;
            inserted += 1;
            is_red = 1;
            codec = red_codec;
        }
        if (is_rtx) {
            // mux into RTX if negotiated
            // mobile sdk use 96(rtx/vp8) and 99(rtx/h264) as rtx pt, which dont accept rtx packet
            // todo: remove these restriction once mobile sdk accept such pt
            if (codec->rtx_enabled /* && codec->rtx_pt != 96 && codec->rtx_pt != 99 */) {
                if (rtc->is_dumping_rtp) {
                    if (is_red) {
                        dump_rtp_red(rtc, "rtx muxing", snd_buf, len);
                    } else {
                        dump_rtp(rtc, "rtx muxing", snd_buf, len);
                    }
                }
                uint16_t osn = be_get_u16(snd_buf + 2);
                // make space for osn
                if (inserted) {
                    memmove(snd_buf + hdr_size + 2, snd_buf + hdr_size, inserted);
                }
                be_set_u16(osn, snd_buf + hdr_size);
                inserted += 2;
                // translate seq
                uint16_t seq = local_xtrans->next_rtx_seq++;
                be_set_u16(seq, snd_buf + 2);
                // translate pt
                snd_buf[1] &= 0x80;
                snd_buf[1] |= codec->rtx_pt;
                // translate ssrc
                be_set_u32(local_xtrans->local_sources[1].src_id, snd_buf + 8);
            } else {
                // rtx not negotiated
                is_rtx = false;
            }
        }

        if (codec_id == rtc_codec_id_ulpfec) {
            // modify pt recovery
            // when fec is protecting two packets with same payload, pt recovery is 0,
            // we will get fec_codec == -1
            // then no modification needed here
            if (fec_codec_id != rtc_codec_id_unknown) {
                rtc_codec_t* fec_codec = rtc_codecs_find_by_id(rtc->codecs, fec_codec_id);
                if (fec_codec) {
                    buf[hdr_size + 1] &= 0x80;
                    buf[hdr_size + 1] |= fec_codec->pt;
                }
            }
            // modify sn base
            uint16_t sn_base = be_get_u16(buf+hdr_size+2);
            sn_base = translate_seq(local_xtrans, sn_base);
            be_set_u16(sn_base, buf + hdr_size + 2);
        }

        // all red/rtx things done, let's append the payload
        memcpy(snd_buf + hdr_size + inserted, buf + hdr_size, len - hdr_size);
        len += inserted;

        if (rtc->is_dumping_rtp) {
            if (codec_id == rtc_codec_id_ulpfec) {
                dump_fec(snd_buf + hdr_size + inserted, len - hdr_size);
            }
            if (is_rtx) {
                dump_rtp_rtx(rtc, "send", snd_buf, len);
            } else if (is_red) {
                dump_rtp_red(rtc, "send", snd_buf, len);
            } else {
                dump_rtp(rtc, "send", snd_buf, len);
            }
        }

        ret = xdtls_srtp_protect(xtrans->dtls, snd_buf, len);
        if(ret <= 0){
            rtc_dbgi("srtp protect rtp fail with %d, stream_type=%d, mid=%s, seq=%u, buf=%p len=%d", ret, local_xtrans->stream_type, xtrans->mid, seq, snd_buf, len);
            ret = -1;
            break;
        }

        len = ret;
//      todo: restore cache
//        if(local_xtrans->sent_cache && !is_rtx){
//            xmcache_put_rtp(local_xtrans->sent_cache, seq, !IS_KEY_FRAME, codec_id, buf, len);
//        }

        if (rtc->batch != nullptr) {
            std::vector<BatchMessage> msgs;
            BatchSenderAppend(rtc->batch, snd_buf, len);
            bool more = true;
            while (more) {
                NiceOutputMessage nice_msgs[IOV_MAX+1] = {0, };
                GOutputVector nice_bufs[IOV_MAX+1] = {0, };

                more = BatchSenderGetMessages(rtc->batch, msgs);

                if (msgs.empty()) {
                    break;
                }

                for (size_t i = 0; i < msgs.size(); ++i) {
                    nice_msgs[i].buffers = &nice_bufs[i];
                    nice_msgs[i].n_buffers = 1;
                    nice_bufs[i].buffer = msgs[i].buffer;
                    nice_bufs[i].size = msgs[i].buffer_size;
                }

                GError* err = nullptr;
                auto n_send = nice_agent_send_messages_nonblocking(rtc->agent,
                                                                   xtrans->stream_id, xtrans->comps[RTC_RTP].component_id,
                                                                   nice_msgs, msgs.size(), nullptr, &err);

                if (err != nullptr) {
                    rtc_dbgi("batch send err: %s", err->message);
                    break;
                }

                for (int i = 0; i < n_send; i++) {
                    BatchMessage& message = msgs[i];
                    xtrans->send_bytes_count += message.buffer_size;
                }

                // rtc_dbgi("batch send %u/%u messages", n_send, msgs.size());

                local_xtrans->local_sources[0].packet_count += n_send;
                local_xtrans->sent_rtp_packets += n_send;

                BatchSenderFreeMessages(rtc->batch, msgs);
            }
        } else {
//            int sent = nice_agent_send(rtc->agent, xtrans->stream_id, xtrans->comps[RTC_RTP].component_id, len, (const char *)snd_buf);
//            if(sent < len) {
//                rtc_dbgi("nice_agent_send fail with %d/%d, stream_id=%d, comp_id=%d", sent, len, xtrans->stream_id, xtrans->comps[RTC_RTP].component_id);
//                ret = -1;
//                break;
//            }
//            local_xtrans->local_sources[0].packet_count++;
//            xtrans->send_bytes_count += sent;
//            ++local_xtrans->sent_rtp_packets;
//            local_xtrans->send_rtp_bytes += len;


            GOutputVector out_buf;
            out_buf.buffer = snd_buf;
            out_buf.size = len;

            NiceOutputMessage nice_msg;
            nice_msg.buffers = &out_buf;
            nice_msg.n_buffers = 1;

            GError* err = nullptr;
            auto n_send = nice_agent_send_messages_nonblocking(rtc->agent,
                    xtrans->stream_id, xtrans->comps[RTC_RTP].component_id,
                    &nice_msg, 1, nullptr, &err);

            if (err != nullptr) {
                rtc_dbgi("send err: %s", err->message);
                g_error_free(err);
                break;
            }

            local_xtrans->local_sources[0].packet_count++;
            xtrans->send_bytes_count += (n_send == 1 ? len : 0);
            ++local_xtrans->sent_rtp_packets;
            local_xtrans->send_rtp_bytes += len;
        }

        local_xtrans->sent_rtp_payload_bytes += payload_bytes;
        ret = 0;
    }while(0);
    return ret;
}

static inline
int send_rtcp_data(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans, rtc_nice_transport * xtrans, unsigned char * buf, int len) {
    int ret = -1;
    do{
        if (rtc->is_dumping_rtcp) {
            dump_rtcp(rtc, "send", buf, len);
        }

        ret = xdtls_srtp_protect_rtcp(xtrans->dtls, buf, len);
        if(ret <= 0){
            rtc_dbgi("srtp protect rtcp fail with %d, stream_type=%d, mid=%s", ret, local_xtrans->stream_type, xtrans->mid);
            ret = -1;
            break;
        }
        len = ret;
        
        int sent = nice_agent_send(rtc->agent, xtrans->stream_id, xtrans->comps[RTC_RTCP].component_id, len, (const char *)buf);
        if(sent < len) {
            rtc_dbgi("nice_agent_send fail with %d/%d, stream_id=%d, comp_id=%d", sent, len, xtrans->stream_id, xtrans->comps[RTC_RTCP].component_id);
            ret = -1;
            break;
        }
        local_xtrans->local_sources[0].packet_count++;
        xtrans->send_bytes_count += sent;
        local_xtrans->send_rtcp_bytes += sent;
        ret = 0;
    }while(0);
    return ret;
}



static inline
void send_compound_rtcp_sr(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans, rtc_nice_transport * xtrans){
    uint8_t * buf = rtc->send_work_buf;
    int len = 0;
    uint64_t now = get_timestamp_ms();
    uint32_t ntp_msw = (uint32_t)now/1000 + ((uint32_t)70*365+17) * 86400;
    uint32_t ntp_lsw = (now % 1000) * 4294967;
    len += make_rtcp_sr_info(buf+len
                             , local_xtrans->local_sources[0].src_id
                             , ntp_msw
                             , ntp_lsw
                             //, local_xtrans->ntp_msw
                             //, local_xtrans->ntp_lsw
                             , local_xtrans->rtp_ts
                             , local_xtrans->sent_rtp_packets
                             , local_xtrans->sent_rtp_payload_bytes);
    const char * cname = rtc->obj_id;
    int cname_len = rtc->obj_id_len;
    len += make_rtcp_sdes(buf+len, local_xtrans->local_sources[0].src_id, cname, cname_len);
    
//    if(local_xtrans->remb_bitrate > 0){
//        len += make_rtcp_remb(buf+len,  local_xtrans->local_sources[0].src_id, local_xtrans->remote_sources[0].src_id, local_xtrans->remb_bitrate);
//    }
//    if(local_xtrans->remb_bitrate > 0){
//        len += make_rtcp_esmb(buf+len,  local_xtrans->local_sources[0].src_id, local_xtrans->remote_sources[0].src_id, local_xtrans->remb_bitrate);
//    }
    if(local_xtrans->enable_CNSL){
        len += make_rtcp_CNSL(buf+len,  local_xtrans->local_sources[0].src_id, local_xtrans->CNSL_lv);
        local_xtrans->enable_CNSL = false;
    }

    send_rtcp_data(rtc, local_xtrans, xtrans, buf, len);
}


// RR Info
// Report Block -- if subscribed and with_report == true and rtcp was received in last 3 secs
// REMB Block   -- smallest remb received
//              -- 300kbps if no remb received
// ESMB Block   -- smallest remb received
//              -- 300 if not subscribed
//              -- 0 if subscribed and rtcp was received in last 3 secs
static inline
void send_compound_rtcp_rr(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans, rtc_nice_transport * xtrans, bool with_report) {
    uint8_t *buf = rtc->send_work_buf;
    // RR Info
    int len = 0;
    bool sub_alive = false;
    uint32_t remb_bitrate = 0;
    bool is_video = local_xtrans == &rtc->xtranses[RTC_VIDEO];
    if (is_video) {
        remb_bitrate = rtc->xtranses[RTC_VIDEO].remote_sources[0].remb_bitrate;
    }
    if (local_xtrans->rtcp_handler
        /* && rtc_connector_sub_count(rtc->connector) > 0*/) {
        uint32_t max_sub_rtt = 0;
        uint32_t max_sub_jitter = 0;
        uint8_t max_sub_lost_frac = 0;
        uint32_t max_sub_lost_cum = 0;
        uint64_t now = get_timestamp_ms();
        rtcp_report_t report;
        rtcp_handler_get_report(local_xtrans->rtcp_handler, report);

        uint32_t lost_rate = report.lost_frac;
        lost_rate = (lost_rate * 100) >> 8;
        if(app_config_get()->log_if_lost_rate_ge
            && (lost_rate > app_config_get()->log_if_lost_rate_ge || report.last_lost_rate_avg > app_config_get()->log_if_lost_rate_ge)){
            rtc_dbgi("%s lost_frac=%u->%u(percent), last_lost_rate_avg=%u, highest_seq=%u, threshold=%u(percent)",
                    is_video ? "video":"audio", report.lost_frac, lost_rate, report.last_lost_rate_avg, report.extended_highest_seq, app_config_get()->log_if_lost_rate_ge);
        }

        rtc_connector_each_linker(rtc->connector, [&](rtc_linker_t linker) {
            if (!is_video) {
                return;
            }

            uint32_t linker_remb = rtc_linker_get_video_remb(linker);
            if (remb_bitrate == 0) {
                remb_bitrate = linker_remb;
                //std::cout << std::string(rtc->obj_id) << " init subscriber " << " remb: " << remb_bitrate << std::endl;
            } else {
                if (linker_remb > 0 && linker_remb < remb_bitrate) {
                    remb_bitrate = linker_remb;
                    //std::cout << std::string(rtc->obj_id) << " update subscriber " << " remb: " << remb_bitrate << std::endl;
                }
            }
        });

        rtc_connector_each_linker(rtc->connector, [&](rtc_linker_t linker) {
            auto sub = rtc_linker_get_rtc(linker);
            if (sub == nullptr) {
                return;
            }
            // find related xtrans from subscriber according to local xtrans
            // maybe we can do it directly with stream type, then we dont need to test local xtrans is video or audio
            rtc_nice_transport *sub_xtrans = nullptr;
            if (local_xtrans == &rtc->xtranses[RTC_AUDIO]) {
                sub_xtrans = &sub->xtranses[RTC_AUDIO];
            } else if (local_xtrans == &rtc->xtranses[RTC_VIDEO]) {
                sub_xtrans = &sub->xtranses[RTC_VIDEO];
            }
            // check is reported in time
            if (now - sub_xtrans->local_sources[0].rtcp_recv_ts > 3000) {
                if (rtc->is_dumping_rtcp) {
                    dbgw("|%s| subscriber '%s' rtcp report stale and skipped. sub_xtrans->local_sources[0].rtcp_recv_ts=%lu",
                         rtc_endpoint_get_id(rtc), rtc_endpoint_get_id(sub), sub_xtrans->local_sources[0].rtcp_recv_ts);
                }
                return;
            }
            sub_alive = true;
            // compensate_rtcp
            if (sub_xtrans->local_sources[0].rtt > max_sub_rtt) {
                max_sub_rtt = sub_xtrans->local_sources[0].rtt;
            }
            if (sub_xtrans->local_sources[0].jitter > max_sub_jitter) {
                max_sub_jitter = sub_xtrans->local_sources[0].jitter;
            }
            uint32_t loss = sub_xtrans->local_sources[0].loss_packet_nb_q8;
            uint32_t total = sub_xtrans->local_sources[0].total_packet_nb;
            sub_xtrans->local_sources[0].loss_packet_nb_q8 = 0;
            sub_xtrans->local_sources[0].total_packet_nb = 0;
            uint8_t frac = 0;
            if (total != 0) {
                frac = loss / total;
            }

            if (frac > max_sub_lost_frac) {
                max_sub_lost_frac = frac;
            }
            if (sub_xtrans->local_sources[0].lost_cum > max_sub_lost_cum) {
                max_sub_lost_cum = sub_xtrans->local_sources[0].lost_cum;
            }
        });

        if (with_report) {
            if (sub_alive) {
                if (report.lsr != 0 && report.dlsr > max_sub_rtt) {
                    report.dlsr -= max_sub_rtt;
                }
                if (max_sub_lost_frac != 0) {
                    report.lost_frac = max_sub_lost_frac;
                }
                if (max_sub_lost_cum != 0) {
                    report.lost_cumulative = max_sub_lost_cum;
                }
                if (max_sub_jitter != 0) {
                    report.inter_arrival_jitter = max_sub_jitter;
                }
            }

            if (rtc->lost_0) {
                report.lost_cumulative = 0;
                report.lost_frac = 0;
            }

            // for testing rtt
//            uint32_t high16 = report.lsr >> 16;
//            if(high16 > 1){
//                high16 -= 1;
//            }
//            report.lsr &= 0x0000FFFF;
//            report.lsr |= high16 << 16;
//            report.dlsr = 1;
            
            //rtc_dbgi("adjust publisher rtcp ssrc=%X, +rtt=%ums, jitter=%u, lost=%u/%u", local_xtrans->local_sources[0].src_id, (max_sub_rtt * 1000) >> 16, report.inter_arrival_jitter, report.lost_frac, report.lost_cumulative);
            len += make_rtcp_rr_info(buf, local_xtrans->local_sources[0].src_id, 1);
            len += make_rtcp_report_block(buf + len,
                                          local_xtrans->remote_sources[0].src_id,
                                          report.lost_frac,
                                          report.lost_cumulative,
                                          report.extended_highest_seq,
                                          report.inter_arrival_jitter,
                                          report.lsr,
                                          report.dlsr);
        } else {
            len += make_rtcp_rr_info(buf, local_xtrans->local_sources[0].src_id, 0);
        }
    } else {
        len += make_rtcp_rr_info(buf, local_xtrans->local_sources[0].src_id, 0);
    }

    const char * cname = rtc->obj_id;
    int cname_len = rtc->obj_id_len;
    len += make_rtcp_sdes(buf+len, local_xtrans->local_sources[0].src_id, cname, cname_len);
    if (is_video) {
        rtc->remb_log_counter++;
        if (remb_bitrate > 0) {
            len += make_rtcp_remb(buf+len, local_xtrans->local_sources[0].src_id, local_xtrans->remote_sources[0].src_id, local_xtrans->remote_sources[1].src_id, remb_bitrate);
            if (rtc->remb_log_counter % 30 == 0) {
                rtc_dbgi("feedback remb (subscriber's lowest) %ukbps to ssrc %u", remb_bitrate/1000, local_xtrans->remote_sources[0].src_id);
            }
        } else {
            remb_bitrate = 300000;
            len += make_rtcp_remb(buf+len, local_xtrans->local_sources[0].src_id, local_xtrans->remote_sources[0].src_id, local_xtrans->remote_sources[1].src_id,  remb_bitrate);
            if (rtc->remb_log_counter % 30 == 0) {
                rtc_dbgi("feedback remb (default) %ukbps to ssrc %u", remb_bitrate/1000, local_xtrans->remote_sources[0].src_id);
            }
        }
//        rtc_dbgi("send remb(%u) %u >> [%u, %u]",
//                 remb_bitrate/1000, local_xtrans->local_sources[0].src_id, local_xtrans->remote_sources[0].src_id, local_xtrans->remote_sources[1].src_id);
    }

//    rtc_dbgi("rr remb R-%d:sublive-%d:rtcesmb-%d:is_video-%d %u >> %u %ukbps ",
//            with_report, sub_alive, rtc->experiment_esmb, is_video,
//            local_xtrans->local_sources[0].src_id, local_xtrans->remote_sources[0].src_id,
//            remb_bitrate/1000);

    if (is_video && rtc->experiment_esmb) {
        if (sub_alive) {
            if (remb_bitrate > 0) {
                len += make_rtcp_esmb(buf+len,  local_xtrans->local_sources[0].src_id, local_xtrans->remote_sources[0].src_id, remb_bitrate);
            } else {
                len += make_rtcp_esmb(buf+len,  local_xtrans->local_sources[0].src_id, local_xtrans->remote_sources[0].src_id, 0);
            }
        } else {
            len += make_rtcp_esmb(buf+len,  local_xtrans->local_sources[0].src_id, local_xtrans->remote_sources[0].src_id, 300000);
        }

//        rtc_dbgi("send esmb(%u) %u >> %u",
//                 local_xtrans->local_sources[0].src_id, local_xtrans->remote_sources[0].src_id,
//                 sub_alive ? (remb_bitrate <= 0 ? 0 : remb_bitrate/1000) : 300);
    }
    send_rtcp_data(rtc, local_xtrans, xtrans, buf, len);
}

/**
 * 不管是pub 还是 sub，都会定时调用这个方法。
 * 可看 rtc_endpoint_create 和 slow_timer_handler
 *
 * @param rtc
 * @param local_xtrans
 * @param xtrans
 */
static inline
void handle_report(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans, rtc_nice_transport * xtrans){
    
    if(!xtrans || !local_xtrans->enabled || !xtrans->dtls){
        return;
    }
    
    if(!xdtls_srtp_is_ready(xtrans->dtls)){
        return;
    }

    if(local_xtrans->ntp_lsw > 0 || local_xtrans->ntp_msw > 0 || rtc->force_send_sr){
        //pub的sr会更新他的sub rtc的>ntp_msw ntp_lsw (可看 rtc_endpoint_set_ntp）
        send_compound_rtcp_sr(rtc, local_xtrans, xtrans);
    }

    send_compound_rtcp_rr(rtc, local_xtrans, xtrans, true);
    
}

static
gboolean slow_timer_handler(gpointer user_data){
    rtc_endpoint_t rtc = (rtc_endpoint_t) user_data;
    int64_t now = get_timestamp_ms();

    if (rtc->is_send_report && ((now - rtc->last_report_time) >= 400)) {
        handle_report(rtc, &rtc->xtranses[RTC_VIDEO], rtc->video_xtrans);
        handle_report(rtc, &rtc->xtranses[RTC_AUDIO], rtc->audio_xtrans);
    }


    return TRUE;
}

static
int rtc_endpoint_add_local_stream(rtc_endpoint_t rtc, int mlineindex, const char * name, rtc_nice_transport * xtrans, std::uint32_t &firstUdpPort){
    int ret = -1;
    do{
        int comp_count = 2;
        // Create a new stream with one component
        guint stream_id = nice_agent_add_stream(rtc->agent, comp_count);
        if (stream_id == 0){
            rtc_dbge("Failed to add stream");
            ret = -1;
            break;
        }
        rtc_dbgv("pre-add stream %s, %d", name, stream_id);
        
        xtrans->owner = rtc;
        xtrans->mid = name;
        nice_agent_set_stream_name (rtc->agent, stream_id, xtrans->mid );
        
        
        // Attach to the component to receive the data
        // Without this call, candidates cannot be gathered
        ret = 0;
        for(int ci = 0; ci < comp_count; ci++){
            int comp_id = ci+1;
            
            if(rtc->min_port > 0 && rtc->max_port >= rtc->min_port){
                nice_agent_set_port_range (rtc->agent, stream_id, comp_id, rtc->min_port, rtc->max_port);
            }
            
            
            
            gboolean b = nice_agent_attach_recv(rtc->agent, stream_id, comp_id,
                                                rtc->main_context, cb_nice_recv, rtc);
            if(!b){
                rtc_dbge("nice_agent_attach_recv fail, stream_id=%d, comp_id=%d, nice_mux=%d", stream_id, comp_id, app_config_get()->nice_mux);
                ret = -1;
                break;
            }
            rtc_dbgi("nice_agent_attach_recv ret=%d", b);
        }
        if(ret) break;
        
        
        // Start gathering local candidates
        if (!nice_agent_gather_candidates(rtc->agent, stream_id)){
            rtc_dbge("Failed to start candidate gathering, stream_id=%d, nice_mux=%d", stream_id, app_config_get()->nice_mux);
            ret = -1;
            break;
        }
        
        xtrans->enabled = 1;
        xtrans->mlineindex = mlineindex;
        xtrans->stream_id = stream_id;
        xtrans->comps[RTC_RTP].component_id = 1;
        xtrans->comps[RTC_RTCP].component_id = 2;
        
        xdtls_role drole = rtc->rtc_role == RTC_ROLE_OFFER ? XDTLS_ROLE_SERVER : XDTLS_ROLE_CLIENT;
        //xtrans->dtls = xdtls_srtp_create(NULL, NULL, XDTLS_HASH_SHA256, XDTLS_ROLE_CLIENT, on_sending_dtls_data, xtrans);
        xtrans->dtls = xdtls_srtp_create(NULL, NULL, XDTLS_HASH_SHA256, drole, on_sending_dtls_data, xtrans);
        if(!xtrans->dtls){
            rtc_dbge("create dtls-srtp fail!");
            ret = -1;
            break;
        }
        rtc_dbgi("created stream dtls-srtp [%s] [%s]. nice_mux=%d", xtrans->mid, xdtls_srtp_get_id(xtrans->dtls), app_config_get()->nice_mux);
        
        xtrans->dtls_timer = g_timeout_source_new(100);
        g_source_set_callback(xtrans->dtls_timer, dtls_timeout_handler, xtrans, NULL);
        g_source_attach(xtrans->dtls_timer, rtc->main_context);
        
        
        if(!xtrans->local_candidate_done){
            rtc_dbge("Failed to gather candidate, stream_id=%d, nice_mux=%d", stream_id, app_config_get()->nice_mux);
            ret = -1;
            break;
        }
        
        
        if (!nice_agent_get_local_credentials(rtc->agent, stream_id,
                                              &xtrans->local_ufrag, &xtrans->local_password)){
            rtc_dbge("nice_agent_get_local_credentials fail, stream_id=%d", stream_id);
            ret = -1;
            break;
        }
        rtc_dbgv("stream[%s] local_ufrag = [%s]", name, xtrans->local_ufrag);
        rtc_dbgv("stream[%s] local_password = [%s]", name, xtrans->local_password);
        
        
        ret = 0;
        for(int compi= 0; compi < comp_count; compi++){
            rtc_nice_component * comp = &xtrans->comps[compi];
            int comp_id = compi+1;
            comp->candidate_strings = rtc_endpoint_pull_comp_candidates(rtc, stream_id, comp_id, &comp->candidate_count, firstUdpPort);
            if(!comp->candidate_strings){
                rtc_dbge("pull candidates fail, stream_id=%d, comp_id=%d", stream_id, comp_id);
                ret = -1;
                break;
            }
            
            rtc_dbgv("pull candidates, stream_id=%d, comp_id=%d, candidates=%d", stream_id, comp_id, comp->candidate_count);
            for(int cdi = 0; cdi < comp->candidate_count; cdi++){
                rtc_dbgv("  local cand[%d]=[%s]", cdi, comp->candidate_strings[cdi]);
            }
        }
        if(ret) break;
        
        ret = 0;
    }while(0);
    
    if(ret == 0){
        rtc_dbgv("successfully add stream %s", name);
    }else{
        rtc_dbge("fail to add stream %s", name);
    }
    return ret;
}

//通过proxy生成的rtcId，助力 xswitch ssrc生成。
//proxy保证了，在每个xswitch中rtcId的唯一
//proxy保证了 每个xswitch 最大支持 65535个 rtc，可以覆盖 单端口（端口复用）和 不复用
//xswitch ssrc 生成规则：matchId的后十三位+三位+rtcId或port
//matchId 需要指定 集群上不能重复，不指定时 解决 ipv4后两段
//xswitch udp mux时，一个proxy 于 一个xswitch 对应，否则，ssrc有重复的危险，因为proxy 只能保证每个xswitch实例rtcId唯一
std::uint32_t nextSSRC(rtc_endpoint_t &rtc, std::uint32_t firstPort, std::uint8_t index){
    app_config *config = app_config_get();
    int machineId = config->machineId & 0x1fff; // 2^13 -1

    if(!config->nice_mux){
        return machineId << (16 + 3) | (index & 0x7) << 16  | firstPort;
    }

    return machineId << (16 + 3) | (index & 0x7) << 16  | (atoi(rtc->obj_id) & 0xffff);
}


static
int rtc_endpoint_init_transport(rtc_endpoint_t rtc, rtc_role_type rtc_role){
    int ret = -1;
    const char * local_ip = rtc->media_ip;
//    local_ip = "172.17.1.197";
//    local_ip = "172.17.1.58";
    
    do{
        if(rtc->rtc_role != RTC_ROLE_NONE){
            rtc_dbge("already set role before");
            ret = -1;
            break;
        }
        
        rtc->rtc_role = rtc_role;
        int controlling = rtc->rtc_role == RTC_ROLE_OFFER ? 1 : 0;
        
        rtc->agent = nice_agent_new(rtc->main_context, NICE_COMPATIBILITY_RFC5245);
        if (!rtc->agent){
            rtc_dbge("Failed to create agent");
            ret = -1;
            break;
        }
        
        g_object_set(G_OBJECT(rtc->agent), "controlling-mode", controlling, NULL);
        g_object_set(G_OBJECT(rtc->agent), "ice-tcp", 0, NULL);
        g_object_set(G_OBJECT(rtc->agent), "ice-udp", 1, NULL);
//        g_object_set(G_OBJECT(rtc->agent), "max-connectivity-checks", 16, NULL); // DON'T set this!!!
        
        if(local_ip && local_ip[0] != '\0'){
            NiceAddress addr;
            nice_address_init(&addr);
            nice_address_set_from_string(&addr, local_ip);
            gboolean a = nice_agent_add_local_address(rtc->agent, &addr);
            if(!a){
                rtc_dbge("Failed to add local address %s", local_ip);
                ret = -1;
                break;
            }
        }
        
        // Connect to the signals
        g_signal_connect(G_OBJECT(rtc->agent), "candidate-gathering-done", G_CALLBACK(cb_candidate_gathering_done), rtc);
        g_signal_connect(G_OBJECT(rtc->agent), "new-selected-pair", G_CALLBACK(cb_new_selected_pair), rtc);
        g_signal_connect(G_OBJECT(rtc->agent), "component-state-changed", G_CALLBACK(cb_component_state_changed), rtc);

        std::uint32_t firstPort = 0;

        if(!rtc->remote_sdpinfo){
            ret = rtc_endpoint_add_local_stream(rtc, 0,  "audio", &rtc->xtranses[RTC_AUDIO], firstPort);
            if(ret) break;
            ret = rtc_endpoint_add_local_stream(rtc, 1,  "video", &rtc->xtranses[RTC_VIDEO], firstPort);
            if(ret) break;
        }else{
            int count = 0;
            if(rtc->remote_sdpinfo->audio_stream){
                ret = rtc_endpoint_add_local_stream(rtc, rtc->remote_sdpinfo->audio_stream->mlineindex, rtc->remote_sdpinfo->audio_stream->mid, &rtc->xtranses[RTC_AUDIO], firstPort);
                if(ret) break;
                count++;
            }
            
            if(rtc->remote_sdpinfo->video_stream){
                ret = rtc_endpoint_add_local_stream(rtc, rtc->remote_sdpinfo->video_stream->mlineindex, rtc->remote_sdpinfo->video_stream->mid, &rtc->xtranses[RTC_VIDEO], firstPort);
                if(ret) break;
                count++;
            }
            rtc->local_stream_count = count;
        }

        rtc_dbgi("Rtc endpoint inet %s -> %s:%d", rtc->obj_id, local_ip, firstPort);

        rtc->xtranses[RTC_AUDIO].local_sources[0].src_id = nextSSRC(rtc, firstPort, 0); // 754039198;
        rtc->xtranses[RTC_VIDEO].local_sources[0].src_id = nextSSRC(rtc, firstPort, 2); // 1843363638;
        rtc->xtranses[RTC_VIDEO].local_sources[1].src_id = nextSSRC(rtc, firstPort, 3); // 21607102;
        rtc_dbgi("rtc xtranses. %s -> audio:[%u, %u], video:[%u, %u]",
                 rtc->obj_id,
                 rtc->xtranses[RTC_AUDIO].local_sources[0].src_id,
                 rtc->xtranses[RTC_AUDIO].local_sources[1].src_id,
                 rtc->xtranses[RTC_VIDEO].local_sources[0].src_id,
                 rtc->xtranses[RTC_VIDEO].local_sources[1].src_id);
        
        ret = 0;
    }while(0);
    
    if(ret){
        rtc_endpoint_uninit_transports(rtc);
    }
    
    return ret;

}

static inline
void rtc_endpoint_bundle_transports(rtc_endpoint_t rtc){
    rtc->is_local_bundle = 1;

    if(rtc->remote_sdpinfo
        && rtc->remote_sdpinfo->sdp_bundles
        && rtc->audio_xtrans
        && rtc->video_xtrans){
        std::string bundle_line(rtc->remote_sdpinfo->sdp_bundles);
        int audioIndex = bundle_line.find(rtc->audio_xtrans->mid);
        int videoIndex = bundle_line.find(rtc->video_xtrans->mid);

        //add by yangss, 2019.09.25
        //1v1 语音呼叫，android 3.5.5 被叫时，听不到主叫声音，sdk 发过来的pranswer中 bundle_line=[a=group:BUNDLE audio]
        //ios chrome 均无这个问题
        if(audioIndex < 0){
            audioIndex = 1000000;
        }
        if(videoIndex < 0){
            videoIndex = 1000000;
        }
        //add end

        if(rtc->audio_xtrans && rtc->video_xtrans){
            rtc_dbgi("bundle xtrans, attach transport audio:%d, video:%d. bundle_line=[%s]",
                    audioIndex, videoIndex, rtc->remote_sdpinfo->sdp_bundles);

            rtc_stream_type xtranse_type;
            if(audioIndex < videoIndex){ //audio 在前，video bind to audio
                rtc->video_xtrans = rtc->audio_xtrans;
                xtranse_type = RTC_VIDEO;
            }else{
                rtc->audio_xtrans = rtc->video_xtrans;
                xtranse_type = RTC_AUDIO;
            }

            destroy_component_candidate_strings(&rtc->xtranses[xtranse_type].comps[RTC_RTP]);
            destroy_component_candidate_strings(&rtc->xtranses[xtranse_type].comps[RTC_RTCP]);

            rtc->xtranses[xtranse_type].comps[RTC_RTP].candidate_count = 0;
            rtc->xtranses[xtranse_type].comps[RTC_RTCP].candidate_count = 0;

            rtc->destroy_xtranse_type = xtranse_type;

            rtc_dbgi("1 xtrans bundle, not use xtranse_type=%d, video_xtrans mid=%s-%p, audio_xtrans mid=%s-%p",
                    xtranse_type, rtc->video_xtrans->mid, rtc->video_xtrans, rtc->audio_xtrans->mid, rtc->audio_xtrans);
        }

        return;
    }

    if(rtc->audio_xtrans && rtc->video_xtrans){
        rtc_dbgi("bundle xtrans, attach video to audio transport");
        rtc->video_xtrans = rtc->audio_xtrans;

        destroy_component_candidate_strings(&rtc->xtranses[RTC_VIDEO].comps[RTC_RTP]);
        destroy_component_candidate_strings(&rtc->xtranses[RTC_VIDEO].comps[RTC_RTCP]);

        rtc->xtranses[RTC_VIDEO].comps[RTC_RTP].candidate_count = 0;
        rtc->xtranses[RTC_VIDEO].comps[RTC_RTCP].candidate_count = 0;

        rtc->destroy_xtranse_type = RTC_VIDEO;
        rtc_dbgi("2 bundle xtrans, not use xtranse_type=%d, video_xtrans mid=%s-%p, audio_xtrans=%s-%p",
                RTC_VIDEO, rtc->video_xtrans->mid, rtc->video_xtrans, rtc->audio_xtrans->mid, rtc->audio_xtrans);
    }
}

static inline
void rtc_endpoint_mux_rtcp(rtc_endpoint_t rtc, rtc_nice_transport * local_xtrans){
    local_xtrans->comps[RTC_RTCP].component_id = local_xtrans->comps[RTC_RTP].component_id;
    destroy_component_candidate_strings(&local_xtrans->comps[RTC_RTCP]);
    local_xtrans->comps[RTC_RTCP].candidate_count = 0;
}

static
int rtc_endpoint_decide_transport(rtc_endpoint_t rtc, xsdp_stream * remote_stream, rtc_nice_transport * local_xtrans, rtc_nice_transport ** pxtrans){
    if(remote_stream){
        rtc_dbgi("remote -> stream %s rtcp-mux=%d, [%u, %u]",
                local_xtrans->mid, remote_stream->is_rtcpmux, remote_stream->ssrcs[0], remote_stream->ssrcs[1]);
        local_xtrans->enabled = 1;
        local_xtrans->remote_sources[0].src_id = remote_stream->ssrcs[0];
        local_xtrans->remote_sources[1].src_id = remote_stream->ssrcs[1];
        *pxtrans = local_xtrans;
        if(remote_stream->is_rtcpmux){
            rtc_endpoint_mux_rtcp(rtc, local_xtrans);
        }
    }else{
        rtc_dbgi("non-exist stream at remote, disable local %s", local_xtrans->mid);
        local_xtrans->enabled = 0;
        *pxtrans = NULL;
    }
    return 0;
}



int rtc_endpoint_decide_transports(rtc_endpoint_t rtc){
    int ret = -1;
    do{
        if(!rtc->remote_sdpinfo){
            rtc_dbge("decide transport but no remote sdp");
            ret = -1;
            break;
        }
        
        rtc_endpoint_decide_transport(rtc, rtc->remote_sdpinfo->audio_stream, &rtc->xtranses[RTC_AUDIO], &rtc->audio_xtrans);
        rtc_endpoint_decide_transport(rtc, rtc->remote_sdpinfo->video_stream, &rtc->xtranses[RTC_VIDEO], &rtc->video_xtrans);
        
        // check bundle and attach video to audio transport when true
        if(rtc->remote_sdpinfo->is_bundle ){
            rtc_endpoint_bundle_transports(rtc);
        }
    
        if(rtc->video_xtrans != &rtc->xtranses[RTC_VIDEO]){
            rtc_nice_transport * xtrans = &rtc->xtranses[RTC_VIDEO];
            destroy_dtls_timer(xtrans);
        }
    
        if(rtc->audio_xtrans != &rtc->xtranses[RTC_AUDIO]){
            rtc_nice_transport * xtrans = &rtc->xtranses[RTC_AUDIO];
            destroy_dtls_timer(xtrans);
        }

        rtc_dbgi("decide xtrans: video_xtrans=%p, audio_xtrans=%p", rtc->video_xtrans, rtc->audio_xtrans);
        
        ret = 0;
        
    }while(0);
    return ret;
}

void rtc_endpoint_get_drop(rtc_endpoint_t rtc, bool& drop_audio, bool& drop_video){
    drop_audio = rtc->drop_audio;
    drop_video = rtc->drop_video;
}
void rtc_endpoint_set_drop(rtc_endpoint_t rtc, bool drop_audio, bool drop_video){
    rtc->drop_audio = drop_audio;
    rtc->drop_video = drop_video;
}

void rtc_endpoint_delete(rtc_endpoint_t rtc){
    if(!rtc) return;
    
    rtc_endpoint_close_tlv(&rtc->tlv1);
    rtc_dbgi("rtc delete, close tlv");
    
    if(rtc->connector){
        rtc_connector_delete(rtc->connector);
        rtc->connector = NULL;
    }
    rtc_dbgi("rtc delete, delete connector, if rtc is pub");
    
    if(rtc->check_timer != NULL) {
        g_source_destroy(rtc->check_timer);
        g_source_unref(rtc->check_timer);
        rtc->check_timer = NULL;
    }
    rtc_dbgi("rtc delete, destroy check_timer");

    rtc_endpoint_call_listeners_destroy(rtc);
    rtc_dbgi("rtc delete, call listeners: destroy");
    
    rtc_endpoint_uninit_transports(rtc);
    rtc_dbgi("rtc delete, uninit transports");
    
    if(rtc->remote_sdpinfo){
        xsdp_free_info(rtc->remote_sdpinfo);
    }
    rtc_dbgi("rtc delete, xsdp free info");
    
    if(rtc->rtcp_parser){
        rtcp_parser_delete(rtc->rtcp_parser);
        rtc->rtcp_parser = NULL;
    }
    rtc_dbgi("rtc delete, delete rtcp parser");

    if (rtc->rtp_factory) {
        delete rtc->rtp_factory;
        rtc->rtp_factory = nullptr;
    }
    rtc_dbgi("rtc delete, delete rtp factory");

    if (rtc->ulpfec_decoder) {
        delete rtc->ulpfec_decoder;
        rtc->ulpfec_decoder = nullptr;
    }
    rtc_dbgi("rtc delete, delete ulpfec decoder");

    for (int i = 0; i < 2; ++i) {
        if (rtc->xtranses[i].rtcp_handler) {
            rtcp_handler_destroy(rtc->xtranses[i].rtcp_handler);
            rtc->xtranses[i].rtcp_handler = nullptr;
        }
    }
    rtc_dbgi("rtc delete, rtcp handler destroy");

    // after all callbacks unregistered
    if(rtc->listeners){
//        g_slist_free_full(rtc->listeners, (GDestroyNotify)&g_free);
        g_queue_free_full(rtc->listeners, (GDestroyNotify)&g_free);
        rtc->listeners = NULL;
    }
    rtc_dbgi("rtc delete, free listeners");

    if (rtc->user_data) {
        for (auto d : *rtc->user_data) {
            g_free(d.second);
        }
        delete rtc->user_data;
        rtc->user_data = nullptr;
    }

    if (rtc->codecs) {
        rtc_codecs_free(rtc->codecs);
        rtc->codecs = nullptr;
    }
    rtc_dbgi("rtc delete, free codecs");

    if (rtc->rtp_extens_audio) {
        RtpExtensionMap::destroy(rtc->rtp_extens_audio);
        rtc->rtp_extens_audio = nullptr;
    }

    if (rtc->rtp_extens_video) {
        RtpExtensionMap::destroy(rtc->rtp_extens_video);
        rtc->rtp_extens_video = nullptr;
    }
    rtc_dbgi("rtc delete, destroy rtp_extens");

    if (rtc->sub_estimator) {
        delete rtc->sub_estimator;
        rtc->sub_estimator = nullptr;
    }

    if (rtc->obj_id) {
        g_free(rtc->obj_id);
        rtc->obj_id = nullptr;
        rtc->obj_id_len = 0;
    }

    if (rtc->batch) {
        BatchSenderDelete(rtc->batch);
        rtc->batch = nullptr;
    }

//    if (rtc->pub_estimator) {
//        delete rtc->pub_estimator;
//        rtc->pub_estimator = nullptr;
//    }

    free(rtc);
}

int rtc_endpoint_send_stream_data(rtc_endpoint_t rtc, int stream_type,
                                  rtc_codec_id_t codec, rtc_codec_id_t fec_codec,
                                  int is_rtcp, int is_rtx,
                                  unsigned char * buf, int len) {
    int ret = -1;
    do{
//        rtc_endpoint_heart_beat(rtc);
        
        if(stream_type >= RTC_STREAM_TYPE_MAX){
            rtc_dbge("unknown stream type %d", stream_type);
            ret = -1;
            break;
        }
        
        rtc_nice_transport * xtrans = NULL;
        rtc_nice_transport * local_xtrans = NULL;
        ret = get_use_xtrans(rtc, stream_type, local_xtrans, xtrans);
        if(ret){
            break;
        }

        if(!xtrans->dtls){
            ret = -1;
            break;
        }
        if(!xdtls_srtp_is_ready(xtrans->dtls)){
            ret = -1;
            break;
        }

        memcpy(rtc->send_work_buf, buf, len);

        if(!is_rtcp){
            ret = send_rtp_data(rtc, local_xtrans, xtrans, codec, fec_codec, rtc->send_work_buf, len, is_rtx);
        }else{
// todo: 这个函数可以直接修改内存，没必要返回一段内存
//            buf = rtc_endpoint_filter_rtcp_before_send(rtc, local_xtrans, send_buf, &len);
//            if(!buf || len <= 0){
//                ret = 0;
//                break;
//            }

                if (stream_type == RTC_AUDIO) {
                    replace_rtcp_placeholder_to_ssrc(rtc->send_work_buf, len, placeholder::audio, local_xtrans);
                } else if (stream_type == RTC_VIDEO) {
                    replace_rtcp_placeholder_to_ssrc(rtc->send_work_buf, len, placeholder::video, local_xtrans);
                }
                ret = send_rtcp_data(rtc, local_xtrans, xtrans, rtc->send_work_buf, len);
        }
        if(ret ) break;
        
        
        ret = 0;
        
    }while(0);
    
    return ret;
}

rtc_endpoint_t rtc_endpoint_create(GMainContext * main_context,
                                   const std::string& rtcId,
                                   const char * media_ip,
                                   int min_port, int max_port,
                                   bool is_dumping_rtp, bool is_dumping_rtcp, int max_video_bitrate, int min_video_bitrate,
                                   bool is_translating_fec, bool disable_h264, bool enable_fec, bool compensate_rtcp,
                                   bool experiment_esmb, bool lost_0, bool enable_bwe, bool force_send_sr,
                                   bool send_nack, int nack_cache_size,
                                   bool drop_audio, bool drop_video) {
    rtc_endpoint_t rtc = NULL;
    int ret = -1;

    do{
        ZERO_ALLOC(rtc, rtc_endpoint_t, sizeof(struct rtc_endpoint_st));
        rtc->create_ts = g_get_real_time();
        rtc->main_context = main_context;
        rtc->media_ip = media_ip;
        rtc->min_port = min_port;
        rtc->max_port = max_port;
        rtc->obj_id = g_strdup(rtcId.c_str());
        rtc->obj_id_len = rtcId.size();

        for(int i = 0; i < 2; i++){
            rtc->xtranses[i].stream_type = i;
            rtc->xtranses[i].mlineindex = -1;
        }
        // TODO: random ssrc
//        rtc->xtranses[RTC_AUDIO].local_sources[0].src_id = nextSSRC(); // 754039198;
//        rtc->xtranses[RTC_VIDEO].local_sources[0].src_id = nextSSRC(); // 1843363638;
//        rtc->xtranses[RTC_VIDEO].local_sources[1].src_id = nextSSRC(); // 21607102;


        if (nack_cache_size < 0) {
            nack_cache_size = 0;
        } else if (nack_cache_size > 128) {
            nack_cache_size = 128;
        }
        if (nack_cache_size != 0) {
            rtc->xtranses[RTC_VIDEO].sent_cache = xmcache_create(nack_cache_size, 1);
        }
        //rtc->xtranses[RTC_VIDEO].nack_req_cache = xmcache_create(0, 0);

        rtc->rtp_factory = new RtpFactory();

        rtc->check_timer = g_timeout_source_new(1000);
        g_source_set_callback(rtc->check_timer, slow_timer_handler, rtc, NULL);
        g_source_attach(rtc->check_timer, rtc->main_context);
        
        rtc->rtcp_parser = rtcp_parser_create();
        rtc->is_dumping_rtp = is_dumping_rtp;
        rtc->is_dumping_rtcp = is_dumping_rtcp;
        rtc->max_video_bitrate = max_video_bitrate;
        rtc->min_video_bitrate = min_video_bitrate;
        rtc->is_translating_fec = is_translating_fec;
        rtc->disable_h264 = disable_h264;
        rtc->enable_fec = enable_fec;
        rtc->compensate_rtcp = compensate_rtcp;
        rtc->experiment_esmb = experiment_esmb;
        rtc->lost_0 = lost_0;
        rtc->enable_bwe = enable_bwe;
        // this controls if we produce tlv for all rtc endpoint
        rtc->force_record = false;
        // not subscribed: 1
        // subscribed: fbpass ? 0 : 1
        rtc->is_send_report = 1;
        // TODO: remove this code
//        rtc_endpoint_register_callback(rtc, NULL, &rtc_listener);
        rtc->remote_audio_ssrc = 0;
        rtc->remote_audio_ssrc_overrided = false;
        rtc->force_send_sr = force_send_sr;
        rtc->enable_nack_handler = send_nack;
        if (app_config_get()->batch_send) {
            rtc->batch = BatchSenderNew(rtc->obj_id,
                    app_config_get()->batch_send_packets,
                    app_config_get()->batch_send_duration);
        }

        rtc->drop_audio = drop_audio;
        rtc->drop_video = drop_video;
        rtc->destroy_xtranse_type = RTC_STREAM_TYPE_MAX;

        rtc_dbgi("rtc created. audio_xtrans=%p, video_xtrans=%p, xtranses[video=%p, audio=%p]",
                rtc->audio_xtrans, rtc->video_xtrans, &rtc->xtranses[1], &rtc->xtranses[0]);
        ret = 0;
    }while(0);
    
    if(ret){
        rtc_endpoint_delete(rtc);
        rtc = NULL;
    }
    
    
    return rtc;
}


static
int rtc_endpoint_set_stream_ices(rtc_endpoint_t rtc, xsdp_stream * stream, rtc_nice_transport * xtrans){
    
    if(!stream) return 0;
    if(xtrans->mlineindex < 0 || !xtrans->enabled) return 0;
    
    int ret = 0;
    if(stream->ice_ufrag && stream->ice_pwd){
        ret = nice_agent_set_remote_credentials(rtc->agent, xtrans->stream_id, stream->ice_ufrag, stream->ice_pwd);
        if(!ret){
            rtc_dbge("fail to set remote ice credentials, stream_id=%d, ice_ufrag=[%s], ice_pwd=[%s]", xtrans->stream_id, stream->ice_ufrag, stream->ice_pwd);
            ret = -1;
            return ret;
        }
        ret = 0;
        rtc_dbgv("ok to set remote ice credentials, stream_id=%d, ice_ufrag=[%s], ice_pwd=[%s]", xtrans->stream_id, stream->ice_ufrag, stream->ice_pwd);
    }

    
    
    GSList *remote_candidates = NULL;
    GSList * cand = stream->str_candidates;
    ret = 0;
    for(int n = 0; cand;){
        n++;
        char * str = (char *)cand->data;
//        rtc_dbgi("pares remote candidate: [%s]", str);
        NiceCandidate * c = nice_agent_parse_remote_candidate_sdp (rtc->agent,
                                                                   xtrans->stream_id,
                                                                   str);
//        rtc_dbgi("parse remote candidate result: c=%p", c);
        if(!c){
            rtc_dbge("fail to parse remote candidate: [%s]", str);
            ret = -1;
            break;
        }
        rtc_dbgi("add remote candidates in sdp: [%s]", str);
        remote_candidates = g_slist_prepend(remote_candidates, c);
        cand = g_slist_next(cand);
    }
    
    
    if(remote_candidates){
        int component_id = 1;
        ret = nice_agent_set_remote_candidates (rtc->agent, xtrans->stream_id, component_id, remote_candidates);
        if(ret < 1){
            rtc_dbge("fail to set remote candidates of stream %s", stream->stream_name);
            ret = -1;
        }else{
            ret = 0;
        }
    }
    
    
    if (remote_candidates != NULL){
        g_slist_free_full(remote_candidates, (GDestroyNotify)&nice_candidate_free);
        remote_candidates = NULL;
    }
    return ret;
}


rtc_endpoint_callback_link_t rtc_endpoint_register_callback(rtc_endpoint_t rtc, void * context, rtc_endpoint_callbacks * callbacks_){
    rtc_endpoint_callbacks * callbacks = (rtc_endpoint_callbacks *)g_malloc0(sizeof(rtc_endpoint_callbacks));
    *callbacks = *callbacks_;
    callbacks->context = context;
//    rtc->listeners = g_slist_prepend (rtc->listeners, callbacks);
    if(!rtc->listeners){
        rtc->listeners = g_queue_new();
    }
    g_queue_push_tail(rtc->listeners, callbacks);
    return g_queue_peek_tail_link(rtc->listeners);
}

void rtc_endpoint_unregister_callback(rtc_endpoint_t rtc, rtc_endpoint_callback_link_t link){
    GList * list = (GList *) link;
    rtc_endpoint_callbacks * callbacks = (rtc_endpoint_callbacks *) list->data;
    g_free(callbacks);
    g_queue_delete_link(rtc->listeners, list);
}

const char * rtc_endpoint_get_id(rtc_endpoint_t rtc){
    return rtc->obj_id;
}

gint64 rtc_endpoint_get_create_ts(rtc_endpoint_t rtc){
    return rtc->create_ts;
}


void _rtc_endpoint_setup_rtcp_handler(rtc_endpoint_t rtc) {
    if (rtc->remote_sdpinfo && rtc->remote_sdpinfo->audio_stream && !rtc->xtranses[RTC_AUDIO].rtcp_handler) {
        // ssrc already replaced to placeholder
        // rtc->xtranses[RTC_AUDIO].rtcp_handler = rtcp_handler_new(rtc, rtc->remote_sdpinfo->audio_stream->ssrcs[0]);
        rtc->xtranses[RTC_AUDIO].rtcp_handler = rtcp_handler_new(rtc, rtc->remote_sdpinfo->audio_stream->ssrcs[0], placeholder::audio.remote.pri);
    }
    if (rtc->remote_sdpinfo && rtc->remote_sdpinfo->video_stream && !rtc->xtranses[RTC_VIDEO].rtcp_handler) {
        // ssrc already replaced to placeholder
        // rtc->xtranses[RTC_VIDEO].rtcp_handler = rtcp_handler_new(rtc, rtc->remote_sdpinfo->video_stream->ssrcs[0]);
        rtc->xtranses[RTC_VIDEO].rtcp_handler = rtcp_handler_new(rtc, rtc->remote_sdpinfo->video_stream->ssrcs[0], placeholder::video.remote.pri);
    }
}

int rtc_endpoint_process_offer(rtc_endpoint_t rtc, const char * sdp, int sdp_len){
    int ret = -1;
    // const char * json="";
    // int json_len = 0;
    GSList *remote_candidates = NULL;
    
    do {
        
        if(rtc->remote_sdpinfo){
            rtc_dbge("already got remote sdp");
            ret = -1;
            break;
        }
        
        if(rtc->rtc_role != RTC_ROLE_NONE){
            rtc_dbge("process offer but conflict role %d", rtc->rtc_role);
            ret = -1;
            break;
        }
        
        //        // simon test
        //        const char * cand_sdp_str="a=candidate:4279727527 1 udp 2122260223 172.17.3.234 58655 typ host generation 0 ufrag YozxHukkAa6K7eZ2";
        //        NiceCandidate * cc = nice_agent_parse_remote_candidate_sdp (rtc->agent, 1, cand_sdp_str);
        //        g_message("==> parse candidate cc=%p", cc);

        
//        ret = rtc_endpoint_maybe_generate_local_ice(rtc);
//        if (ret){
//            rtc_dbge("generate answer but local credentials fail");
//            ret = -1;
//            break;
//        }
        
        ret = xsdp_parse(sdp, sdp_len, &rtc->remote_sdpinfo);
        if(ret){
            rtc_dbge("parse sdp error %d, sdp: %.*s", ret, sdp_len, sdp);
            ret = -1;
            break;
        }
        if(rtc->remote_sdpinfo->stream_count == 0){
            rtc_dbge("parse sdp error of no stream, sdp: %.*s", sdp_len, sdp);
            ret = -1;
            break;
        }
        xsdp_print(rtc->remote_sdpinfo, "offer");
        negotiate(rtc);
        rtc->codecs = rtc->remote_sdpinfo->codecs;
        rtc->remote_sdpinfo->codecs = nullptr;

        ret = rtc_endpoint_init_transport(rtc, RTC_ROLE_ANSWER);
        if(ret){
            rtc_dbge("process offer but init transport fail %d", ret);
            ret = -1;
            break;
        }

        ret = rtc_endpoint_decide_transports(rtc);
        if(ret){
            rtc_dbge("decide transport fail, %d", ret);
            ret = -1;
            break;
        }
        
//        xsdp_stream * stream = NULL;
//        
//        if(rtc->remote_sdpinfo->audio_stream && rtc->remote_sdpinfo->audio_stream->ice_ufrag && rtc->remote_sdpinfo->audio_stream->ice_pwd){
//            stream = rtc->remote_sdpinfo->audio_stream;
//        }else if(rtc->remote_sdpinfo->video_stream && rtc->remote_sdpinfo->video_stream->ice_ufrag && rtc->remote_sdpinfo->video_stream->ice_pwd){
//            stream = rtc->remote_sdpinfo->video_stream;
//        }else{
//            rtc_dbge("parse sdp->sdp error of no ice credentials, json: %.*s", json_len, json);
//            ret = -1;
//            break;
//        }
//        ret = nice_agent_set_remote_credentials(rtc->agent, stream->stream_id, stream->ice_ufrag, stream->ice_pwd);
//        if(!ret){
//            rtc_dbge("nice_agent_set_remote_credentials error, stream_id=%d, ice_ufrag=%s, ice_pwd=%s", stream->stream_id, stream->ice_ufrag, stream->ice_pwd);
//            ret = -1;
//            break;
//        }
        
        ret = rtc_endpoint_set_stream_ices(rtc, rtc->remote_sdpinfo->audio_stream, &rtc->xtranses[RTC_AUDIO]);
        if(ret){
            rtc_dbge("set audio stream error, ret=%d", ret);
            ret = -1;
            break;
        }
        
        ret = rtc_endpoint_set_stream_ices(rtc, rtc->remote_sdpinfo->video_stream, &rtc->xtranses[RTC_VIDEO]);
        if(ret){
            rtc_dbge("set video stream error, ret=%d", ret);
            ret = -1;
            break;
        }
        
        rtc->remote_sdp_state = SDP_STATE_OFFER;
        _rtc_endpoint_setup_rtcp_handler(rtc);
        ret = 0;
    } while (0);
    
    if (remote_candidates != NULL){
        g_slist_free_full(remote_candidates, (GDestroyNotify)&nice_candidate_free);
    }
    
    return ret;
}

static const char* _rtc_endpoint_negotiate_sendrecv(const xsdp_stream* stream) {
    switch (stream->sendrecv) {
        case xsdp_sendrecv_type::sendrecv: return "sendrecv";
        case xsdp_sendrecv_type::sendonly: return "recvonly";
        case xsdp_sendrecv_type::recvonly: return "sendonly";
    }
    return "sendrecv";
}

int rtc_endpoint_generate_answer_audio_section(rtc_endpoint_t rtc, int is_pranswer){
    rtc_nice_transport * local_xtrans = NULL;
    rtc_nice_transport * xtrans = NULL;

    local_xtrans = &rtc->xtranses[RTC_AUDIO];
    xtrans = local_xtrans;
    if(xtrans->mlineindex >= 0){
        // uint32_t ssrc = xtrans->local_sources[0].src_id;
        if(rtc->audio_xtrans){
            xtrans = rtc->audio_xtrans;
        }

        if (rtc->remote_sdpinfo) {
            if (rtc->remote_sdpinfo->audio_stream) {
                rtc->local_sdp_len += xsdp_format_audio_sdp(rtc->local_sdp + rtc->local_sdp_len, SDP_MAX - rtc->local_sdp_len,
                                                            rtc->rtp_extens_audio,
                                                            rtc->codecs, local_xtrans->mid,
                                                            rtc->rtc_role == RTC_ROLE_OFFER ? "actpass" : "active",
                                                            xtrans->local_ufrag, xtrans->local_password,
                                                            xdtls_srtp_get_local_fingerprint_str(xtrans->dtls),
                                                            is_pranswer ? "inactive" : _rtc_endpoint_negotiate_sendrecv(rtc->remote_sdpinfo->audio_stream),
                                                            rtc->remote_sdpinfo->audio_stream->is_rtcpmux == 1,
                                                            local_xtrans->local_sources[0].src_id, rtc->obj_id, "ARDAMS", "audio");
            }
        } else {
            rtc->local_sdp_len += xsdp_format_audio_sdp(rtc->local_sdp + rtc->local_sdp_len, SDP_MAX - rtc->local_sdp_len,
                                                        rtc->rtp_extens_audio,
                                                        rtc->codecs, local_xtrans->mid,
                                                        rtc->rtc_role == RTC_ROLE_OFFER ? "actpass" : "active",
                                                        xtrans->local_ufrag, xtrans->local_password,
                                                        xdtls_srtp_get_local_fingerprint_str(xtrans->dtls),
                                                        is_pranswer ? "inactive" : "sendrecv",
                                                        true,
                                                        local_xtrans->local_sources[0].src_id, rtc->obj_id, "ARDAMS", "audio");
        }
    }

    return 0;
}

int rtc_endpoint_generate_answer_video_section(rtc_endpoint_t rtc, int is_pranswer){
    rtc_nice_transport * local_xtrans = NULL;
    rtc_nice_transport * xtrans = NULL;

    local_xtrans = &rtc->xtranses[RTC_VIDEO];
    xtrans = local_xtrans;
    if(xtrans->mlineindex >= 0){
        uint32_t ssrc = xtrans->local_sources[0].src_id;
        uint32_t ssrc_rtx = xtrans->local_sources[1].src_id;
        if(rtc->video_xtrans){
            xtrans = rtc->video_xtrans;
        }
        if (rtc->remote_sdpinfo) {
            if (rtc->remote_sdpinfo->video_stream) {
                rtc->local_sdp_len += xsdp_format_video_sdp(rtc->local_sdp + rtc->local_sdp_len, SDP_MAX - rtc->local_sdp_len,
                                                            rtc->rtp_extens_video,
                                                            rtc->codecs, rtc->enable_fec,
                                                            rtc->max_video_bitrate, rtc->min_video_bitrate, local_xtrans->mid,
                                                            rtc->rtc_role == RTC_ROLE_OFFER ? "actpass" : "active",
                                                            xtrans->local_ufrag, xtrans->local_password,
                                                            xdtls_srtp_get_local_fingerprint_str(xtrans->dtls),
                                                            is_pranswer ? "inactive" : _rtc_endpoint_negotiate_sendrecv(rtc->remote_sdpinfo->video_stream),
                                                            rtc->remote_sdpinfo->video_stream->is_rtcpmux == 1,
                                                            ssrc, ssrc_rtx, rtc->obj_id, "ARDAMS", "video");
            }
        } else {
            rtc->local_sdp_len += xsdp_format_video_sdp(rtc->local_sdp + rtc->local_sdp_len, SDP_MAX - rtc->local_sdp_len,
                                                        rtc->rtp_extens_video,
                                                        rtc->codecs, rtc->enable_fec,
                                                        rtc->max_video_bitrate, rtc->min_video_bitrate, local_xtrans->mid,
                                                        rtc->rtc_role == RTC_ROLE_OFFER ? "actpass" : "active",
                                                        xtrans->local_ufrag, xtrans->local_password,
                                                        xdtls_srtp_get_local_fingerprint_str(xtrans->dtls),
                                                        is_pranswer ? "inactive" : "sendrecv",
                                                        true,
                                                        ssrc, ssrc_rtx, rtc->obj_id, "ARDAMS", "video");
        }
    }

    return 0;
}

int rtc_endpoint_generate_answer(rtc_endpoint_t rtc, int is_pranswer){
    
    int ret = -1;
    
    
    do {
        // TODO: codec payload
        // const char * cname = rtc->obj_id;
        
        char * buf = rtc->local_sdp;
        int bufsize = sizeof(rtc->local_sdp);
        
        rtc->local_sdp_len = 0;
        
        const char * msid = "ARDAMS";
        // common
        rtc->local_sdp_len += snprintf(buf+rtc->local_sdp_len, bufsize-rtc->local_sdp_len,
                                       "%s" // common
                                       ,
                                       "v=0\r\n"
                                       "o=- 3674263760 3674263760 IN IP4 0.0.0.0\r\n"
                                       "s=RTC XSwitch\r\n"
                                       "c=IN IP4 0.0.0.0\r\n"
                                       "t=0 0\r\n"
                                       //"a=msid-semantic: WMS ARDAMS\r\n"
                                       );
        rtc->local_sdp_len += snprintf(buf+rtc->local_sdp_len, bufsize-rtc->local_sdp_len,"a=msid-semantic: WMS %s\r\n",msid);
        
        if(rtc->is_local_bundle) {
            rtc->local_sdp_len += snprintf(buf+rtc->local_sdp_len,
                    bufsize-rtc->local_sdp_len, "a=group:BUNDLE");

            if(rtc->destroy_xtranse_type !=  RTC_STREAM_TYPE_MAX){
                //唯一被赋值的地方 是在 rtc_endpoint_bundle_transports 中
                // !=  RTC_STREAM_TYPE_MAX此时两个xtranses都存在
                rtc->local_sdp_len += snprintf(buf+rtc->local_sdp_len,
                                               bufsize-rtc->local_sdp_len, " %s",
                                               rtc->xtranses[rtc->destroy_xtranse_type == RTC_AUDIO ? RTC_VIDEO : RTC_AUDIO].mid);

                rtc->local_sdp_len += snprintf(buf+rtc->local_sdp_len,
                                               bufsize-rtc->local_sdp_len, " %s",
                                               rtc->xtranses[rtc->destroy_xtranse_type].mid);
            }else{
                if (rtc->audio_xtrans) {
                    rtc->local_sdp_len += snprintf(buf+rtc->local_sdp_len,
                                                   bufsize-rtc->local_sdp_len, " %s",
                                                   rtc->xtranses[RTC_AUDIO].mid);
                }

                if (rtc->video_xtrans) {
                    rtc->local_sdp_len += snprintf(buf+rtc->local_sdp_len,
                                                   bufsize-rtc->local_sdp_len, " %s",
                                                   rtc->xtranses[RTC_VIDEO].mid);
                }
            }

            rtc->local_sdp_len += snprintf(buf+rtc->local_sdp_len, bufsize-rtc->local_sdp_len,
                    "\r\n");
        }

        if(rtc->destroy_xtranse_type !=  RTC_STREAM_TYPE_MAX){
            if(rtc->destroy_xtranse_type == RTC_VIDEO){
                // audio
                rtc_endpoint_generate_answer_audio_section(rtc, is_pranswer);
                // video
                rtc_endpoint_generate_answer_video_section(rtc, is_pranswer);
            }
            if(rtc->destroy_xtranse_type == RTC_AUDIO){
                // video
                rtc_endpoint_generate_answer_video_section(rtc, is_pranswer);
                // audio
                rtc_endpoint_generate_answer_audio_section(rtc, is_pranswer);
            }
        }else{
            // audio
            rtc_endpoint_generate_answer_audio_section(rtc, is_pranswer);
            // video
            rtc_endpoint_generate_answer_video_section(rtc, is_pranswer);
        }

        rtc_dbgv("local sdp=%s", buf);
        ret = 0;
    } while (0);
    
    return ret;
    
}

int rtc_endpoint_generate_offer(rtc_endpoint_t rtc, std::list<std::string>& hints){
    int ret = -1;
    do{
        if(rtc->rtc_role != RTC_ROLE_NONE){
            rtc_dbge("generate offer but conflict role %d", rtc->rtc_role);
            ret = -1;
            break;
        }
        
        ret = rtc_endpoint_init_transport(rtc, RTC_ROLE_OFFER);
        if(ret){
            rtc_dbge("process offer but init transport fail %d", ret);
            ret = -1;
            break;
        }
        
        rtc->audio_xtrans = &rtc->xtranses[RTC_AUDIO];
        rtc->video_xtrans = &rtc->xtranses[RTC_VIDEO];
        rtc_dbgi("generate offer. init xtrans: video_xtrans=%p, audio_xtrans=%p", rtc->video_xtrans, rtc->audio_xtrans);
        rtc_endpoint_bundle_transports(rtc);
        rtc_endpoint_mux_rtcp(rtc, &rtc->xtranses[RTC_AUDIO]);
        rtc_endpoint_mux_rtcp(rtc, &rtc->xtranses[RTC_VIDEO]);

        rtc->codecs = rtc_codecs_default(hints);
        rtc->rtp_extens_audio = RtpExtensionMap::default_audio();
        rtc->rtp_extens_video = RtpExtensionMap::default_video();
        ret = rtc_endpoint_generate_answer(rtc, 0);
    }while(0);

    return ret;
}

int rtc_endpoint_process_answer(rtc_endpoint_t rtc, const char * sdp, int sdp_len, int is_pranswer){
    int ret = -1;
    const char * json="";
    int json_len = 0;
    GSList *remote_candidates = NULL;
    
    do {
        
        if(rtc->rtc_role != RTC_ROLE_OFFER){
            rtc_dbge("process answer but conflict role %d", rtc->rtc_role);
            ret = -1;
            break;
        }
        
        if(rtc->remote_sdpinfo){
            if(rtc->remote_sdp_state == SDP_STATE_ANSWER){
                rtc_dbge("process answer but already got remote sdp");
                ret = -1;
                break;
            }
            if(rtc->remote_sdp_state == SDP_STATE_PRANSWER){
                if(is_pranswer){
                    rtc_dbge("process pranswer but already got pranswer");
                    ret = -1;
                    break;
                }else{
                    rtc_dbge("process answer but already got pranswer, skip");
                    ret = 0;
                    break;
                }

            }
        }
        
        if(is_pranswer){
            rtc_dbgi("process pranswer");
        }
        
        ret = xsdp_parse(sdp, sdp_len, &rtc->remote_sdpinfo);
        if(ret){
            rtc_dbge("parse sdp->sdp error %d, json: %.*s", ret, json_len, json);
            ret = -1;
            break;
        }
        if(rtc->remote_sdpinfo->stream_count == 0){
            rtc_dbge("parse sdp->sdp error of no stream, json: %.*s", json_len, json);
            ret = -1;
            break;
        }
        xsdp_print(rtc->remote_sdpinfo, "answer");
        negotiate(rtc);
        if (rtc->codecs) {
            rtc_codecs_free(rtc->codecs);
        }
        rtc->codecs = rtc->remote_sdpinfo->codecs;
        rtc->remote_sdpinfo->codecs = nullptr;
        
        ret = rtc_endpoint_decide_transports(rtc);
        if(ret){
            rtc_dbge("process answer but decide transport fail, %d", ret);
            ret = -1;
            break;
        }
        
        ret = rtc_endpoint_set_stream_ices(rtc, rtc->remote_sdpinfo->audio_stream, &rtc->xtranses[RTC_AUDIO]);
        if(ret){
            rtc_dbge("set audio stream error, ret=%d", ret);
            ret = -1;
            break;
        }
        
        ret = rtc_endpoint_set_stream_ices(rtc, rtc->remote_sdpinfo->video_stream, &rtc->xtranses[RTC_VIDEO]);
        if(ret){
            rtc_dbge("set video stream error, ret=%d", ret);
            ret = -1;
            break;
        }

        _rtc_endpoint_setup_rtcp_handler(rtc);
        ret = 0;
    } while (0);
    
    if(ret == 0){
        rtc->remote_sdp_state = is_pranswer ? SDP_STATE_PRANSWER : SDP_STATE_ANSWER;
    }
    
    if (remote_candidates != NULL){
        g_slist_free_full(remote_candidates, (GDestroyNotify)&nice_candidate_free);
    }
    
    return ret;
}


int rtc_endpoint_add_remote_candidate(rtc_endpoint_t rtc, int mlineindex, const char * mid, const char * candidate_str, int retryModifyCands){
    int ret = -1;
    GSList *remote_candidates = NULL;
    
    do {
        
        if(!rtc->remote_sdpinfo){
            rtc_dbge("add remote candidate but no sdp");
            ret = -1;
            break;
        }
        
        rtc_nice_transport * xtrans = rtc_endpoint_get_transport_by_remote_mlineindex(rtc, mlineindex);
        if(!xtrans){
            rtc_dbge("fail to find transport by remote mlineindex=%d", mlineindex);
            ret = -1;
            break;
        }
        
        int stream_id = xtrans->stream_id;
        rtc_dbgi("setting remote candidate: stream_id=[%d], mlineindex=[%d], mid=[%s], cand=[%s]", stream_id, mlineindex, mid?mid:"NULL", candidate_str);

        NiceCandidate * c = nice_agent_parse_remote_candidate_sdp (rtc->agent,
                                                                   stream_id,
                                                                   candidate_str);
        if(!c){
            rtc_dbge("fail to parse remote candidate: [%s]", candidate_str);

            if(retryModifyCands){
                std::string new_candidate_str;

                //candidate:978030784 1 udp 2113937151 192.168.3.4 56527 typ host generation 0 ufrag HhLy network-cost 999
                std::string tmpcand(candidate_str);

                std::regex word_regex("(\\S+)");
                auto words_begin = std::sregex_iterator(tmpcand.begin(), tmpcand.end(), word_regex);
                auto words_end = std::sregex_iterator();

                auto words_count = std::distance(words_begin, words_end);
                auto index = 0;
                for (std::sregex_iterator i = words_begin; i != words_end; ++i, index++) {
                    std::smatch match = *i;
                    std::string match_str = match.str();

                    if(index == 4){
                        new_candidate_str += "255.255.255.255";
                    }else{
                        new_candidate_str += match_str;
                    }

                    if(index < words_count - 1){
                        new_candidate_str += " ";
                    }
                }

                rtc_dbge("modify remote candidate, retry: [%s] -> [%s]", candidate_str, new_candidate_str.c_str());
                return rtc_endpoint_add_remote_candidate(rtc, mlineindex, mid, new_candidate_str.c_str(), 0);
            }else{
                ret = -1;
                break;
            }
        }
        
        remote_candidates = g_slist_prepend(remote_candidates, c);
        guint component_id = c->component_id;
        ret = nice_agent_set_remote_candidates (rtc->agent, stream_id, component_id, remote_candidates);
        if(ret < 1){
            rtc_dbge("fail to set remote candidate: stream_id=[%d], comp_id=[%d], cand=[%s]", stream_id, component_id, candidate_str);
            ret = -1;
            break;
        }
        rtc_dbgi("set remote candidate: stream_id=%d, comp_id=%d, cand=[%s]", stream_id, component_id, candidate_str);
        
        ret = 0;
    } while (0);
    
    if (remote_candidates != NULL){
        g_slist_free_full(remote_candidates, (GDestroyNotify)&nice_candidate_free);
        remote_candidates = NULL;
    }
    
    return ret;
}


const char * rtc_endpoint_get_local_sdp(rtc_endpoint_t rtc, int *psdp_len){
    if(rtc->local_sdp_len > 0){
        if(psdp_len){
            *psdp_len = rtc->local_sdp_len;
        }
        return rtc->local_sdp;
    }
    return NULL;
}

int rtc_endpoint_get_mline_count(rtc_endpoint_t rtc){
    int count = 0;
    if(rtc->xtranses[RTC_AUDIO].mlineindex >= 0){
        count += 1;
    }
    if(rtc->xtranses[RTC_VIDEO].mlineindex >= 0){
        count += 1;
    }
    return count;
}

int rtc_endpoint_get_candidates(rtc_endpoint_t rtc, int stage, int mlineindex, const char ** pmid, int * pcandidate_count, char *** pcandidate){
    
    if(mlineindex < 0 || mlineindex >= 2){
        return -1;
    }
    if(stage > 1) stage = 1;
    rtc_nice_transport * xtrans = &rtc->xtranses[RTC_AUDIO];
    if(xtrans->mlineindex != mlineindex){
        xtrans = &rtc->xtranses[RTC_VIDEO];
        if(xtrans->mlineindex != mlineindex){
            return -1;
        }
    }
    
    if(xtrans->mlineindex >= 0 ){
        *pmid = xtrans->mid;
        *pcandidate_count = xtrans->comps[stage].candidate_count;
        *pcandidate = xtrans->comps[stage].candidate_strings;
    }else{
        *pmid = NULL;
        *pcandidate_count = 0;
        *pcandidate = NULL;
    }
    return 0;
}

int rtc_endpoint_get_stream_cryto_keys(rtc_endpoint_t rtc, int stream_type, unsigned char ** plocal, int * plocal_len, unsigned char ** premote, int *premote_len){
    // TODO:
//    return xdtls_srtp_get_keys(rtc->xtranses[stream_type].dtls, unsigned char ** plocal, int * plocal_len, unsigned char ** premote, int *premote_len);
    return 0;
}

static inline
int rtc_endpoint_reset_seq(rtc_endpoint_t rtc){
    for(int si = 0; si < 2; si++){
        rtc_nice_transport * local_xtrans = &rtc->xtranses[si];
        local_xtrans->is_orgin_seq_valid = 0;
        ++local_xtrans->last_sent_rtp_seq;
        local_xtrans->sent_rtp_seq_base = local_xtrans->last_sent_rtp_seq;
        
    }
    return 0;
}

int rtc_endpoint_connect(rtc_endpoint_t rtc, rtc_endpoint_t sub_rtc, bool audio, bool video){
    if(sub_rtc->pub_rtc) {
        if(sub_rtc->pub_rtc == rtc) {
            rtc_connector_update_sub(rtc->connector, sub_rtc->sub_linker, audio, video);
            return 0;
        } else {
            return -1;
        }
    }
    if(!rtc->connector){
        rtc->connector = rtc_connector_create(rtc, 0);
    }
    sub_rtc->sub_linker = rtc_connector_add_sub(rtc->connector, sub_rtc, audio, video);
    sub_rtc->pub_rtc = rtc;
    
    // reset seq for srtp_protect
    rtc_endpoint_reset_seq(sub_rtc);
    return 0;
}

int rtc_endpoint_reset_pubrtc(rtc_endpoint_t rtc, rtc_endpoint_t pub_rtc){
    if(rtc->pub_rtc == pub_rtc){
        rtc->pub_rtc = NULL;
        rtc->sub_linker = NULL;
        return 0;
    }
    return -1;
}

int rtc_endpoint_subscribe(rtc_endpoint_t rtc, rtc_endpoint_t pub_rtc, bool audio, bool video){
    if (audio || video) {
        rtc_endpoint_connect(pub_rtc, rtc, audio, video);
    } else {
        rtc_connector_remove_sub(pub_rtc->connector, rtc->sub_linker);
    }
    return 0;
}

int rtc_endpoint_unsubscribe(rtc_endpoint_t rtc, rtc_endpoint_t pub_rtc){
    if(rtc->pub_rtc == pub_rtc && rtc->sub_linker && pub_rtc->connector){
        rtc_connector_remove_sub(pub_rtc->connector, rtc->sub_linker);
        return 0; 
    }
    return -1;
}

rtc_endpoint_t rtc_endpoint_get_publisher(rtc_endpoint_t rtc) {
    return rtc->pub_rtc;
}

int rtc_endpoint_enable_report(rtc_endpoint_t rtc, int enabled){
    rtc_dbgi("enable report %d", enabled);
    rtc->is_send_report = enabled;
    return 0;
}

//static inline
//rtc_nice_transport * get_xtrans(rtc_endpoint_t rtc, int stream_type, rtc_nice_transport **plocal_xtrans){
//    *plocal_xtrans = NULL;
//    if(stream_type >= RTC_STREAM_TYPE_MAX){
//        return NULL;
//    }
//    rtc_nice_transport * local_xtrans = &rtc->xtranses[stream_type];
//    if(local_xtrans == &rtc->xtranses[RTC_VIDEO] && rtc->video_xtrans){
//        *plocal_xtrans = local_xtrans;
//        return rtc->video_xtrans;
//    }else if(rtc->audio_xtrans){
//        *plocal_xtrans = local_xtrans;
//        return rtc->audio_xtrans;
//    }else{
//        return NULL;
//    }
//}

int rtc_endpoint_set_ntp(rtc_endpoint_t rtc, int stream_type, uint32_t ntp_msw, uint32_t ntp_lsw, uint32_t rtp_ts){
    int ret = -1;
    do{
        if(stream_type >= RTC_STREAM_TYPE_MAX){
            rtc_dbge("unknown stream type %d", stream_type);
            ret = -1;
            break;
        }

        rtc_nice_transport * xtrans = NULL;
        rtc_nice_transport * local_xtrans = NULL;
        ret = get_use_xtrans(rtc, stream_type, local_xtrans, xtrans);
        if(ret){
            break;
        }

        // setting ts in SR caused video freezed after about 10min, so not setting it here
        // todo: investigate why, did it get wrong ts from wrong ssrc?
        if(rtc->is_send_report && local_xtrans->ntp_msw == 0 && local_xtrans->ntp_lsw == 0){
            local_xtrans->ntp_msw = ntp_msw;
            local_xtrans->ntp_lsw = ntp_lsw;
            //local_xtrans->rtp_ts = rtp_ts;
            handle_report(rtc, local_xtrans, xtrans);
        }else{
            local_xtrans->ntp_msw = ntp_msw;
            local_xtrans->ntp_lsw = ntp_lsw;
            //local_xtrans->rtp_ts = rtp_ts;
        }
        
        ret = 0;
        
    }while(0);
    
    return ret;
}

int rtc_endpoint_req_pli(rtc_endpoint_t rtc){
    rtc_nice_transport * local_xtrans = &rtc->xtranses[RTC_VIDEO];
    rtc_nice_transport * xtrans = rtc->video_xtrans;
    
    if(!xtrans || !local_xtrans->enabled || !xtrans->dtls){
        return -1;
    }
    
    if(!xdtls_srtp_is_ready(xtrans->dtls)){
        return -1;
    }
    
//    rtc_endpoint_heart_beat(rtc);
    
    uint8_t * buf = rtc->send_work_buf;
    int len = 0;
    len += make_rtcp_pli(buf+len, local_xtrans->local_sources[0].src_id, local_xtrans->remote_sources[0].src_id);
//    dump_rtcp(rtc, "sent pli ------> ", buf, len);
    return send_rtcp_data(rtc, local_xtrans, xtrans, buf, len);
}

int rtc_endpoint_set_CNSL(rtc_endpoint_t rtc, int stream_type, uint8_t l) {
    int ret = -1;
    do{
        if(stream_type >= RTC_STREAM_TYPE_MAX){
            rtc_dbge("unknown stream type %d", stream_type);
            ret = -1;
            break;
        }

        if(l < 0 || l > 3) {
            ret = -1;
            break;
        }

        rtc_nice_transport * xtrans = NULL;
        rtc_nice_transport * local_xtrans = NULL;
        ret = get_use_xtrans(rtc, stream_type, local_xtrans, xtrans);
        if(ret){
            break;
        }

        if(!local_xtrans){
            break;
        }
        
        local_xtrans->enable_CNSL = true;
        local_xtrans->CNSL_lv = l;
        ret = 0;
    }while(0);
    
    return ret;
}

int rtc_endpoint_req_nack(rtc_endpoint_t rtc, int stream_type, uint16_t orig_pid, uint16_t orig_blp){
    rtc_nice_transport * local_xtrans = NULL;
    rtc_nice_transport * xtrans = NULL;

    // get_xtrans(rtc, stream_type, &local_xtrans);
    if(get_use_xtrans(rtc, stream_type, local_xtrans, xtrans)){
        return -1;
    }
    if(!xtrans || !local_xtrans->enabled || !xtrans->dtls){
        return -1;
    }
    
    if(!xdtls_srtp_is_ready(xtrans->dtls)){
        return -1;
    }
    
//    rtc_endpoint_heart_beat(rtc);
    
//    uint16_t new_pid;
//    uint16_t new_blp;
//    on_send_nack(rtc, local_xtrans,  xtrans
//                 , orig_pid, orig_blp
//                 , &new_pid, &new_blp);
//    if(new_pid !=0 || new_blp != 0){
        uint8_t * buf = rtc->send_work_buf;
        int len = 0;
        len += make_rtcp_rr_info(buf+len, local_xtrans->local_sources[0].src_id, 0);
        len += make_rtcp_nack(buf+len, local_xtrans->local_sources[0].src_id, local_xtrans->remote_sources[0].src_id, orig_pid, orig_blp);
        return send_rtcp_data(rtc, local_xtrans, xtrans, buf, len);
//    }else{
//        return 0;
//    }
}

void rtc_endpoint_update_remb(rtc_endpoint_t rtc, uint32_t ssrc, uint32_t remb_bitrate) {
    // std::cout << std::string(rtc->obj_id) << " update " << ssrc << " remb: " << remb_bitrate << std::endl;
    // find source by ssrc
    rtc_nice_transport* xtrans = rtc->video_xtrans;
    rtc_nice_transport* local_xtrans = &rtc->xtranses[RTC_VIDEO];
    rtc_transport_source* source = nullptr;
    if (!xtrans) {
        return;
    }

    if (ssrc == rtc->xtranses[RTC_VIDEO].local_sources[0].src_id) {
        source = &rtc->xtranses[RTC_VIDEO].local_sources[0];
    } else if (ssrc == rtc->xtranses[RTC_VIDEO].local_sources[1].src_id) {
        source = &rtc->xtranses[RTC_VIDEO].local_sources[1];
    } else if (ssrc == rtc->xtranses[RTC_VIDEO].remote_sources[0].src_id) {
        source = &rtc->xtranses[RTC_VIDEO].remote_sources[0];
    } else if (ssrc == rtc->xtranses[RTC_VIDEO].remote_sources[1].src_id) {
        source = &rtc->xtranses[RTC_VIDEO].remote_sources[1];
    } else {
        return;
    }

    if (remb_bitrate < (uint32_t)rtc->min_video_bitrate * 1000) {
        remb_bitrate = rtc->min_video_bitrate * 1000;
    }
    uint32_t prev_remb_bitrate = source->remb_bitrate;
    source->remb_bitrate = remb_bitrate;
    if (remb_bitrate < 97 * prev_remb_bitrate / 100) {
        send_compound_rtcp_rr(rtc, local_xtrans, xtrans, false);
    }
}


void rtc_endpoint_send_immediate_remb_for_video_stream(rtc_endpoint_t rtc) {
//    if (ssrc == rtc->xtranses[RTC_VIDEO].local_sources[0].src_id
//            || ssrc == rtc->xtranses[RTC_VIDEO].local_sources[1].src_id) {
        send_compound_rtcp_rr(rtc, &rtc->xtranses[RTC_VIDEO], rtc->video_xtrans, false);
//    }
}

// todo: query rtcp handler for such rtcp report materials
//void rtc_endpoint_update_report(rtc_endpoint_t rtc, int stream_type, uint8_t lost_frac, uint32_t lost_cumulative,
//                               uint32_t extended_highest_seq, uint32_t inter_arrival_jitter, uint32_t lsr,
//                               uint32_t dlsr) {
//    rtc_nice_transport * local_xtrans;
//    rtc_nice_transport * xtrans = get_xtrans(rtc, stream_type, &local_xtrans);
//    if (!xtrans || !local_xtrans) {
//        rtc_dbge("failed to update report due to xtrans not found");
//        return;
//    }
//
//    local_xtrans->lost_frac = lost_frac;
//    local_xtrans->lost_cumulative = lost_cumulative;
//    local_xtrans->extended_highest_seq = extended_highest_seq;
//    local_xtrans->inter_arrival_jitter = inter_arrival_jitter;
//    local_xtrans->lsr = lsr;
//    local_xtrans->dlsr = dlsr;
//}

void rtc_endpoint_set_user_data(rtc_endpoint_t rtc, const char* name, const void * userdata, int size) {
    if (rtc->user_data == nullptr) {
        rtc->user_data = new std::map<std::string, void*>();
    }
    void* a = g_malloc(size);
    memcpy(a, userdata, size);
    rtc->user_data->emplace(std::move(std::string(name)), a);
}

const void * rtc_endpoint_get_user_data(rtc_endpoint_t rtc, const char* name){
    if (rtc->user_data == nullptr) {
        return nullptr;
    }
    std::string n(name);
    auto it = rtc->user_data->find(n);
    if (it == rtc->user_data->end()) {
        return nullptr;
    }
    return it->second;
}

//这里更新了sub rtc local_source
int rtc_endpoint_check_live(rtc_endpoint_t rtc, int64_t now) {
    if (rtc->sub_estimator && rtc->video_xtrans && rtc->pub_rtc) {
        rtc->sub_estimator->UpdateEstimation(now);
        auto br = rtc->sub_estimator->TargetBitrate();
        //更新sub rtc remote remb
        //这个rtc是sub，local  记录了 remote 的 remb
        //1.怎么确定的是 0，2.为什么不是remote source
        //这里更新的时 local source，不会影响  send_compound_rtcp_rr --读的是 remote source
        rtc_endpoint_update_remb(rtc, rtc->xtranses[RTC_VIDEO].local_sources[0].src_id, br);
    }

// publisher side estimation is disable
// use subscriber side estimation only
//    if (rtc->pub_estimator && rtc->video_xtrans) {
//        rtc->pub_estimator->UpdateEstimation(now);
//        auto br = rtc->pub_estimator->TargetBitrate();
//        rtc_endpoint_update_remb(rtc, rtc->xtranses[1].remote_sources[0].src_id, br);
//    }

    if (now - rtc->last_check_live_time < 2000) {
        return 1;
    }

    rtc_endpoint_call_listeners_event(rtc, RTC_EVENT_TIMER, &now, 0);
    
    if(rtc->last_live_seq != rtc->live_seq){
        rtc->last_live_seq = rtc->live_seq;
        rtc->last_check_live_time = now;
        return 1; // still live
    }else{
        const int64_t timeout_ms = 60*1000;
        int64_t elapse = (now-rtc->last_check_live_time);
        if(elapse < timeout_ms){
            return 1; // still live
        }else{
            rtc_dbgi("live timeout %" PRId64 " ms, dead", timeout_ms);
            return 0; // dead
        }
    }
}

int rtc_endpoint_set_filename(rtc_endpoint_t rtc, const char * filename, int filename_length){
    int ret = -1;
    do{
        if(rtc->filename[0]){
            rtc_dbgi("already set filename [%s]", rtc->filename);
            ret = -1;
            break;
        }
        
        if(filename && filename[0] != '\0'){
            if(filename_length > 0){
                memcpy(rtc->filename, filename, filename_length);
                rtc->filename[filename_length] = '\0';
            }else{
                strcpy(rtc->filename, filename);
            }
            
        }else{
            snprintf(rtc->filename, sizeof(rtc->filename), "/tmp/%s.tlv", rtc->obj_id);
        }
        
        //rtc->tlv_with_raw_packet = true;

        if(rtc->is_setup){
            ret = rtc_endpoint_open_tlv(rtc);
            if(ret){
                break;
            }
            if (rtc->sps_pps_len) {
                rtc_endpoint_write_tlv_rtp_rtcp(rtc->tlv1, 0, rtc->sps_pps_buf, rtc->sps_pps_len);
            }
            rtc_endpoint_req_pli(rtc);
        }

        ret = 0;
    }while(0);
    
    return ret;
}

const char* rtc_endpoint_get_filename(rtc_endpoint_t rtc) {
    return rtc->filename;
}

int rtc_endpoint_is_setup(rtc_endpoint_t rtc){
    return rtc->is_setup;
}

int rtc_endpoint_is_offer_role(rtc_endpoint_t rtc){
    return rtc->rtc_role == RTC_ROLE_OFFER;
}

uint8_t* rtc_endpoint_get_h264_sps_pps(rtc_endpoint_t rtc) {
    return rtc->sps_pps_buf;
}

size_t rtc_endpoint_get_h264_sps_pps_len(rtc_endpoint_t rtc) {
    return rtc->sps_pps_len;
}

// TODO we may handle remb like this, I did not change that because I am not sure
// to pass through the SR/RR or broke it like now.
void rtc_endpoint_update_report(rtc_endpoint_t rtc, uint64_t recv_ts, uint32_t ssrc, uint32_t hseq, uint32_t rtt, uint32_t inter_arrival_jitter, uint8_t lost_frac, uint32_t lost_cum) {
    // find source by ssrc
    // bool is_primary_video = false;
    rtc_transport_source* source = nullptr;
    if (rtc->xtranses[RTC_AUDIO].local_sources[0].src_id == ssrc) {
        source = &rtc->xtranses[RTC_AUDIO].local_sources[0];
    } else if (rtc->xtranses[RTC_AUDIO].local_sources[1].src_id == ssrc) {
        source = &rtc->xtranses[RTC_AUDIO].local_sources[1];
    } else if (rtc->xtranses[RTC_VIDEO].local_sources[0].src_id == ssrc) {
        // is_primary_video = true;
        source = &rtc->xtranses[RTC_VIDEO].local_sources[0];
    } else if (rtc->xtranses[RTC_VIDEO].local_sources[1].src_id == ssrc) {
        source = &rtc->xtranses[RTC_VIDEO].local_sources[1];
    } else if (placeholder::audio.local.pri == ssrc) {
        source = &rtc->xtranses[RTC_AUDIO].local_sources[0];
    } else if (placeholder::audio.local.rtx == ssrc) {
        source = &rtc->xtranses[RTC_AUDIO].local_sources[1];
    } else if (placeholder::video.local.pri == ssrc) {
        source = &rtc->xtranses[RTC_VIDEO].local_sources[0];
    } else if (placeholder::video.local.rtx == ssrc) {
        source = &rtc->xtranses[RTC_VIDEO].local_sources[1];
    }else {
        return;
    }

    if (source->hseq == 0) {
        source->hseq = hseq;
    } else {
        uint32_t packet_nb = hseq - source->hseq;
        source->loss_packet_nb_q8 += packet_nb * lost_frac;
        source->total_packet_nb += packet_nb;
        source->hseq = hseq;
    }
    source->rtt = rtt;
    source->jitter = inter_arrival_jitter;
    source->lost_cum = lost_cum;
    source->rtcp_recv_ts = recv_ts;

    // double loss_ratio = (double)source->loss_packet_nb_q8 / 255 / source->total_packet_nb;
//    std::cout << ssrc << " report";
//    std::cout << " hseq " << hseq;
//    std::cout << " lost_frac " << (int)lost_frac;
//    std::cout << " loss " << source->loss_packet_nb_q8 / 255;
//    std::cout << " total " << source->total_packet_nb;
//    std::cout << " loss ratio " << loss_ratio << std::endl;
}

void rtc_endpoint_update_transport_feedback(rtc_endpoint_t rtc, const TransportFeedback &fb) {
    if (rtc->sub_estimator == nullptr) {
        return;
    }
    //        rtc->sub_estimator->on_transport_feedback(fb);
    rtc->sub_estimator->IncomingTransportFeedback(fb);
}

rtc_codecs_t* rtc_endpoint_codecs(rtc_endpoint_t rtc) {
    return rtc->codecs;
}

RtpExtensionMap* rtc_endpoint_audio_extension_map(rtc_endpoint_t rtc) {
    return rtc->rtp_extens_audio;
}

uint32_t rtc_endpoint_get_remote_audio_ssrc(rtc_endpoint_t rtc) {
    if (!rtc) {
        return 0xFFFFFFFF;
    }
    if (rtc->remote_audio_ssrc_overrided) {
        return rtc->remote_audio_ssrc;
    }
    if (!rtc->remote_sdpinfo->audio_stream) {
        return 0xFFFFFFFF;
    }
    return rtc->remote_sdpinfo->audio_stream->ssrcs[0];
}

void rtc_endpoint_override_remote_audio_ssrc(rtc_endpoint_t rtc, uint32_t ssrc) {
    if (!rtc) {
        return;
    }
    rtc->remote_audio_ssrc = ssrc;
    rtc->remote_audio_ssrc_overrided = true;
}

void negotiate(rtc_endpoint_t rtc) {
    std::ostringstream output;
    xsdpinfo* info = rtc->remote_sdpinfo;
    if (rtc->rtp_extens_audio) {
        RtpExtensionMap::destroy(rtc->rtp_extens_audio);
        rtc->rtp_extens_audio = nullptr;
    }
    if (rtc->rtp_extens_video) {
        RtpExtensionMap::destroy(rtc->rtp_extens_video);
        rtc->rtp_extens_video = nullptr;
    }
    if (info->audio_stream && info->audio_stream->rtp_exten_map) {
        rtc->rtp_extens_audio = RtpExtensionMap::create();
        auto ssrc_audio_level_ext_id = info->audio_stream->rtp_exten_map->getId(RtpExtensionType::SSRCAudioLevel);
        if (ssrc_audio_level_ext_id) {
            rtc->rtp_extens_audio->add(RtpExtensionType::SSRCAudioLevel, ssrc_audio_level_ext_id);
        }
        // we have issues sending rtp with such exten (parse_rtp_header_extension)
//        auto csrc_audio_level_ext_id = info->audio_stream->rtp_exten_map->getId(RtpExtensionType::CSRCAudioLevel);
//        if (csrc_audio_level_ext_id) {
//            rtc->rtp_extens_audio->add(RtpExtensionType::CSRCAudioLevel, csrc_audio_level_ext_id);
//        }

        rtc->rtp_extens_audio->for_each([&output,&rtc](RtpExtensionType type, int id) {
            rtc_dbgi("\taudio rtp-header-extension %d %s", id, RtpExtensionMap::uri(type));
        });
    }

    bool use_transport_cc = false;
    if (info->video_stream && info->video_stream->rtp_exten_map) {
        rtc->rtp_extens_video = RtpExtensionMap::create();
        auto transport_seq_num_ext_id = info->video_stream->rtp_exten_map->getId(RtpExtensionType::TransportSequenceNumber);
        auto abs_send_time_ext_id = info->video_stream->rtp_exten_map->getId(RtpExtensionType::AbsSendTime);
        if (transport_seq_num_ext_id && rtc->enable_bwe && info->video_stream->sendrecv != xsdp_sendrecv_type::sendonly) {
            rtc->rtp_extens_video->add(RtpExtensionType::TransportSequenceNumber, transport_seq_num_ext_id);
            use_transport_cc = true;
        } else if (abs_send_time_ext_id) {
            rtc->rtp_extens_video->add(RtpExtensionType::AbsSendTime, abs_send_time_ext_id);
            info->video_stream->abs_send_time_enabled = true;
        }
        rtc->rtp_extens_video->for_each([&output,&rtc](RtpExtensionType type, int id) {
            rtc_dbgi("\tvideo rtp-header-extension %d %s", id, RtpExtensionMap::uri(type));
        });
    }

    // negotiate codecs
    rtc_codecs_negotiate(info->codecs);
    if (info->video_stream && rtc_codecs_video_has_transport_cc(info->codecs) && use_transport_cc) {
        info->video_stream->transport_cc_enabled = true;
    }

    rtc_codecs_dump(info->codecs);

}

void rtc_endpoint_update_receive_deltas(rtc_endpoint_t rtc, uint32_t ssrc, std::vector<bwe::PacketInfo>& packets) {
    // find source by ssrc
    // rtc_transport_source* source = nullptr;
    // if (rtc->xtranses[RTC_AUDIO].local_sources[0].src_id == ssrc) {
    //     source = &rtc->xtranses[RTC_AUDIO].local_sources[0];
    // } else if (rtc->xtranses[RTC_AUDIO].local_sources[1].src_id == ssrc) {
    //     source = &rtc->xtranses[RTC_AUDIO].local_sources[1];
    // } else if (rtc->xtranses[RTC_VIDEO].local_sources[0].src_id == ssrc) {
    //     source = &rtc->xtranses[RTC_VIDEO].local_sources[0];
    // } else if (rtc->xtranses[RTC_VIDEO].local_sources[1].src_id == ssrc) {
    //     source = &rtc->xtranses[RTC_VIDEO].local_sources[1];
    // } else {
    //     return;
    // }

//    if (source->hseq == 0) {
//        source->hseq = hseq;
//    } else {
//        uint32_t packet_nb = hseq - source->hseq;
//        source->loss_packet_nb_q8 += packet_nb * lost_frac;
//        source->total_packet_nb += packet_nb;
//        source->hseq = hseq;
//    }
//    source->rtt = rtt;
//    source->jitter = inter_arrial_jitter;
//    source->lost_cum = lost_cum;
//    source->rtcp_recv_ts = recv_ts;
}

void rtc_endpoint_reset_bytes(rtc_endpoint_t rtc, uint64_t* send, uint64_t* recv) {
    if (send) {
        *send = rtc->xtranses[RTC_AUDIO].send_bytes_count + rtc->xtranses[RTC_VIDEO].send_bytes_count;
    }
    if (recv) {
        *recv = rtc->xtranses[RTC_AUDIO].recv_bytes_count + rtc->xtranses[RTC_VIDEO].recv_bytes_count;
    }
    rtc->xtranses[RTC_AUDIO].send_bytes_count = 0;
    rtc->xtranses[RTC_VIDEO].send_bytes_count = 0;
    rtc->xtranses[RTC_AUDIO].recv_bytes_count = 0;
    rtc->xtranses[RTC_VIDEO].recv_bytes_count = 0;
}

void rtc_endpoint_reset_packets(rtc_endpoint_t rtc, uint64_t* send, uint64_t* recv) {
    if (send) {
        *send = rtc->xtranses[RTC_AUDIO].local_sources[0].packet_count
                + rtc->xtranses[RTC_AUDIO].local_sources[1].packet_count
                + rtc->xtranses[RTC_VIDEO].local_sources[0].packet_count
                + rtc->xtranses[RTC_VIDEO].local_sources[1].packet_count;
    }
    if (recv) {
        *recv = rtc->xtranses[RTC_AUDIO].remote_sources[0].packet_count
                + rtc->xtranses[RTC_AUDIO].remote_sources[1].packet_count
                + rtc->xtranses[RTC_VIDEO].remote_sources[0].packet_count
                + rtc->xtranses[RTC_VIDEO].remote_sources[1].packet_count;
    }

    rtc->xtranses[RTC_AUDIO].local_sources[0].packet_count = 0;
    rtc->xtranses[RTC_AUDIO].local_sources[1].packet_count = 0;
    rtc->xtranses[RTC_VIDEO].local_sources[0].packet_count = 0;
    rtc->xtranses[RTC_VIDEO].local_sources[1].packet_count = 0;

    rtc->xtranses[RTC_AUDIO].remote_sources[0].packet_count = 0;
    rtc->xtranses[RTC_AUDIO].remote_sources[1].packet_count = 0;
    rtc->xtranses[RTC_VIDEO].remote_sources[0].packet_count = 0;
    rtc->xtranses[RTC_VIDEO].remote_sources[1].packet_count = 0;
}

// following fields should never get reset, they are used for billing
void rtc_endpoint_get_bytes(rtc_endpoint_t rtc,
                            uint64_t *audio_send_rtp, uint64_t *audio_send_rtcp, uint64_t* audio_send_dtls,
                            uint64_t *audio_recv_rtp, uint64_t *audio_recv_rtcp, uint64_t* audio_recv_dtls, uint64_t *audio_recv_unknown,
                            uint64_t *video_send_rtp, uint64_t *video_send_rtcp, uint64_t* video_send_dtls,
                            uint64_t *video_recv_rtp, uint64_t *video_recv_rtcp, uint64_t* video_recv_dtls, uint64_t *video_recv_unknown
                            )
{
    *audio_send_rtp     = rtc->xtranses[RTC_AUDIO].send_rtp_bytes;
    *audio_send_rtcp    = rtc->xtranses[RTC_AUDIO].send_rtcp_bytes;
    *audio_send_dtls    = rtc->xtranses[RTC_AUDIO].send_dtls_bytes;
    *audio_recv_rtp     = rtc->xtranses[RTC_AUDIO].recv_rtp_bytes;
    *audio_recv_rtcp    = rtc->xtranses[RTC_AUDIO].recv_rtcp_bytes;
    *audio_recv_dtls    = rtc->xtranses[RTC_AUDIO].recv_dtls_bytes;
    *audio_recv_unknown = rtc->xtranses[RTC_AUDIO].recv_unknown_bytes;

    *video_send_rtp     = rtc->xtranses[RTC_VIDEO].send_rtp_bytes;
    *video_send_rtcp    = rtc->xtranses[RTC_VIDEO].send_rtcp_bytes;
    *video_send_dtls    = rtc->xtranses[RTC_VIDEO].send_dtls_bytes;
    *video_recv_rtp     = rtc->xtranses[RTC_VIDEO].recv_rtp_bytes;
    *video_recv_rtcp    = rtc->xtranses[RTC_VIDEO].recv_rtcp_bytes;
    *video_recv_dtls    = rtc->xtranses[RTC_VIDEO].recv_dtls_bytes;
    *video_recv_unknown = rtc->xtranses[RTC_VIDEO].recv_unknown_bytes;
}

int rtc_endpoint_remote_pub(rtc_endpoint_t pub_rtc, const std::string& sub_rtc_id, bool audio, bool video, const std::string& link_addr,
                            int requireCheckRR, std::vector<uint32_t> sub_peer_audio_ssrcs, std::vector<uint32_t> sub_peer_video_ssrcs) {
    // todo: 用rtc_connector_get 替换 rtc_connector_create / rtc_connector_add_sub
    if (!pub_rtc->connector){
        pub_rtc->connector = rtc_connector_create(pub_rtc, 0);
    }

    return rtc_connector_add_remote_sub(pub_rtc->connector, sub_rtc_id, audio, video, link_addr, requireCheckRR, sub_peer_audio_ssrcs, sub_peer_video_ssrcs);
}

int rtc_endpoint_remote_upub(rtc_endpoint_t pub_rtc, const std::string& sub_rtc_id) {
    if (!pub_rtc->connector) {
        return 0;
    }

    return rtc_connector_remove_remote_sub(pub_rtc->connector, sub_rtc_id);
}

int parse_rtp_header_extension(unsigned char* buf, int size, std::function<bool(uint8_t id, unsigned char* buf, int size)> callback) {
    int r_off = 4;
    int w_off = 4;

    while(r_off < size) {
        if (buf[r_off] == 0) {
            ++r_off;
            ++w_off;
            continue;
        }

        uint8_t id = (buf[r_off] & 0xf0) >> 4;
        uint8_t l = buf[r_off] & 0x0f;

        auto keep = callback(id, buf + r_off, l + 2);
        if (keep) {
            r_off += l + 2;
            w_off += l + 2;
        } else {
            r_off += l + 2;
        }
    }

    if (w_off % 4 != 0) {
        buf[w_off] = 0;
        ++w_off;
        be_set_u16(w_off - 4, buf + 2);
    }

    return w_off;
}

void rtc_endpoint_get_relay_audio_ssrc_list(rtc_endpoint_t rtc,
        std::vector<uint32_t> &ssrcs) {
    ssrcs.clear();
    ssrcs.push_back(rtc->xtranses[RTC_AUDIO].local_sources[0].src_id);
    ssrcs.push_back(rtc->xtranses[RTC_AUDIO].local_sources[1].src_id);
}

void rtc_endpoint_get_relay_video_ssrc_list(rtc_endpoint_t rtc,
        std::vector<uint32_t> &ssrcs) {
    ssrcs.clear();
    ssrcs.push_back(rtc->xtranses[RTC_VIDEO].local_sources[0].src_id);
    ssrcs.push_back(rtc->xtranses[RTC_VIDEO].local_sources[1].src_id);
}

void rtc_endpoint_get_local_audio_ssrc(rtc_endpoint_t rtc, std::vector<uint32_t> &ssrcs) {
    ssrcs.clear();
//    if (rtc->remote_sdpinfo == nullptr
//            || rtc->remote_sdpinfo->audio_stream == nullptr) {
//        return;
//    }

    ssrcs.push_back(rtc->xtranses[RTC_AUDIO].local_sources[0].src_id);
    ssrcs.push_back(rtc->xtranses[RTC_AUDIO].local_sources[1].src_id);
}

void rtc_endpoint_get_local_video_ssrc(rtc_endpoint_t rtc, std::vector<uint32_t> &ssrcs) {
    ssrcs.clear();
//    if (rtc->remote_sdpinfo == nullptr
//            || rtc->remote_sdpinfo->video_stream == nullptr) {
//        return;
//    }
    ssrcs.push_back(rtc->xtranses[RTC_VIDEO].local_sources[0].src_id);
    ssrcs.push_back(rtc->xtranses[RTC_VIDEO].local_sources[1].src_id);
}

void rtc_endpoint_get_remote_audio_ssrc_list(rtc_endpoint_t rtc,
        std::vector<uint32_t> &ssrcs) {
    ssrcs.clear();
    if (rtc->remote_sdpinfo == nullptr
            || rtc->remote_sdpinfo->audio_stream == nullptr) {
        return;
    }
    ssrcs.push_back(rtc->xtranses[RTC_AUDIO].remote_sources[0].src_id);
    ssrcs.push_back(rtc->xtranses[RTC_AUDIO].remote_sources[1].src_id);
}

void rtc_endpoint_get_remote_video_ssrc_list(rtc_endpoint_t rtc,
        std::vector<uint32_t> &ssrcs) {
    ssrcs.clear();
    if (rtc->remote_sdpinfo == nullptr
            || rtc->remote_sdpinfo->video_stream == nullptr) {
        return;
    }
    ssrcs.push_back(rtc->xtranses[RTC_VIDEO].remote_sources[0].src_id);
    ssrcs.push_back(rtc->xtranses[RTC_VIDEO].remote_sources[1].src_id);
}

